1

I'm trying to test some error responses (BadRequest, Unauthorized, ...) with Refit and so I implemented a TestHandler that returns any desired response. The response works fine with an "OK" (HTTP status code 200) response:

public class Program
{
    private static async Task Main()
    {
        var api = RestService.For<ITest>(
            new HttpClient(new TestHandler(HttpStatusCode.OK))
                {
                    BaseAddress = new Uri("https://example.com")
                }
            );
        Console.WriteLine(await api.Test("foo").ConfigureAwait(false));
    }
}

public interface ITest
{
    [Get("/foo/{bar}")]
    Task<string> Test(string bar);
}

public class TestHandler : HttpMessageHandler
{
    private readonly HttpResponseMessage _response;

    public TestHandler(HttpStatusCode httpStatusCode)
        => _response = new HttpResponseMessage(httpStatusCode) { Content = new StringContent("Yay!") };

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        => Task.FromResult(_response);
}

However, whenever I change the response status code to, for example, BadRequest (400), NotFound (404) or Unauthorized (401) Refit throws a NullReferenceException:

Object reference not set to an instance of an object.
   at Refit.DefaultApiExceptionFactory.<CreateExceptionAsync>d__4.MoveNext() in /_/Refit/RefitSettings.cs:line 183
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at Refit.RequestBuilderImplementation.<>c__DisplayClass14_0`2.<<BuildCancellableTaskFuncForMethod>b__0>d.MoveNext() in /_/Refit/RequestBuilderImplementation.cs:line 313
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at RefitTest.Program.<Main>d__0.MoveNext() in C:\Users\Rob\Source\Repos\RefitTest\RefitTest\Program.cs:line 18

This points to RefitSettings.cs line 183. But I can't figure out why a 200 OK would work but any other response won't? What am I doing wrong?

Edit: In an effort to debug this further I cloned Refit and swapped the Refit NuGet package for a project reference to the Refit project. This results in a InvalidOperationException: ITest doesn't look like a Refit interface. Make sure it has at least one method with a Refit HTTP method attribute and Refit is installed in the project. Also, going back a few versions (I've gone to 5.2.4) doesn't help.

RobIII
  • 8,488
  • 2
  • 43
  • 93
  • `This points to RefitSettings.cs line 183. But I can't figure out why a 200 OK would work but any other response won't?` https://github.com/reactiveui/refit/blob/39553f245b929e169809d0be6e77e13a479291a7/Refit/RefitSettings.cs#L173 this method is called for not successfull responses like 400, 404, 401... And probably you are getting `NullReferenceException` because ! (null-forgiving) https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-forgiving` only supported in C# 8+ – demo Sep 21 '21 at 10:41
  • @demo I have a `latest` in my .net 5.0 project file. – RobIII Sep 21 '21 at 11:42
  • @demo Also added `enable`, just in case, no difference. – RobIII Sep 21 '21 at 11:55
  • then it looks like response is null... or I don't understand what/why... – demo Sep 21 '21 at 12:09
  • @demo The response is in the code: `new StringContent("Yay!")` (and, yes, I have tried valid JSON as well, in case Refit would expect JSON). And, as stated in the question: it works fine with a 200 OK. Just not with 4xx statuses (haven't tried 3xx or 5xx yet). – RobIII Sep 22 '21 at 07:31
  • with 3xx and 5xx should be the same like with 4xx as they aren't successful. and there is some similar issue to posted by you https://github.com/reactiveui/refit/issues/1233 maybe it can help you – demo Sep 22 '21 at 10:08

1 Answers1

3

Found it, with help from a colleague! Turns out the HttpResponseMessage needs some/any RequestMessage.

Change

public TestHandler(HttpStatusCode httpStatusCode)
    => _response = new HttpResponseMessage(httpStatusCode)
    { 
        Content = new StringContent("Yay!") 
    };

To:

public TestHandler(HttpStatusCode httpStatusCode)
    => _response = new HttpResponseMessage(httpStatusCode)
    {
        RequestMessage = new(),  // <-- This one here...
        Content = new StringContent("Yay!")
    };

And it works as expected. As in the question (and comments), I was close but, apparently half-asleep, because it's exactly where it pointed me at.

I have submitted an issue here.

RobIII
  • 8,488
  • 2
  • 43
  • 93