6

I am trying to use NSubstitute to mock HttpClient. Here's the code:

public static HttpClient GetHttpClient(bool isSucess = true, string methodType = "GET")
        {
            var mockIHttpMessageHandler = Substitute.For<IMockHttpMessageHandler>();
            var mockHttpMessageHandler = Substitute.For<MockHttpMessageHandler>(mockIHttpMessageHandler);
            var httpResponse = Substitute.For<HttpResponseMessage>();
            httpResponse.Content = new StringContent("\"test\"");
            if (isSucess)
                httpResponse.StatusCode = HttpStatusCode.OK;
            else
                httpResponse.StatusCode = HttpStatusCode.NotFound;

            var mockHttpClient = Substitute.For<HttpClient>(mockHttpMessageHandler);
            mockHttpClient.BaseAddress = new Uri("http://localhost");

            if(methodType != "POST"){
                mockHttpClient.GetAsync(Arg.Any<Uri>()).ReturnsForAnyArgs(httpResponse);
            }
            return mockHttpClient;
        }

However, I got an error at this line:

mockHttpClient.GetAsync(Arg.Any<Uri>()).ReturnsForAnyArgs(httpResponse);

And the error is

NSubstitute.Exceptions.RedundantArgumentMatcherException: 'Some argument specifications (e.g. Arg.Is, Arg.Any) were left over after the last call.

This is often caused by using an argument spec with a call to a member NSubstitute does not handle (such as a non-virtual member or a call to an instance which is not a substitute), or for a purpose other than specifying a call (such as using an arg spec as a return value). For example:

var sub = Substitute.For<SomeClass>();
var realType = new MyRealType(sub);
// INCORRECT, arg spec used on realType, not a substitute:
realType.SomeMethod(Arg.Any<int>()).Returns(2);
// INCORRECT, arg spec used as a return value, not to specify a call:
sub.VirtualMethod(2).Returns(Arg.Any<int>());
// INCORRECT, arg spec used with a non-virtual method:
sub.NonVirtualMethod(Arg.Any<int>()).Returns(2);
// CORRECT, arg spec used to specify virtual call on a substitute:
sub.VirtualMethod(Arg.Any<int>()).Returns(2);

To fix this make sure you only use argument specifications with calls to substitutes. If your substitute is a class, make sure the member is virtual.

Another possible cause is that the argument spec type does not match the actual argument type, but code compiles due to an implicit cast. For example, Arg.Any() was used, but Arg.Any() was required.

NOTE: the cause of this exception can be in a previously executed test. Use the diagnostics below to see the types of any redundant arg specs, then work out where they are being created.

Diagnostic information:

Remaining (non-bound) argument specifications: any Uri

All argument specifications: any Uri

Are they suggesting I need to change the getAsync method? There's no virtual method for GetAsync

Edit:

I have also tried to remove NSubstitute for HttpClient as follows, but I still got the same error:

public static HttpClient GetHttpClient(bool isSucess = true, string methodType = "GET")
            {
                var mockIHttpMessageHandler = Substitute.For<IMockHttpMessageHandler>();
                var mockHttpMessageHandler = Substitute.For<MockHttpMessageHandler>(mockIHttpMessageHandler);
                var httpResponse = Substitute.For<HttpResponseMessage>();
                httpResponse.Content = new StringContent("\"test\"");
                if (isSucess)
                    httpResponse.StatusCode = HttpStatusCode.OK;
                else
                    httpResponse.StatusCode = HttpStatusCode.NotFound;
    
                 var httpClient = new HttpClient(mockHttpMessageHandler);
                httpClient = new Uri("http://localhost");
    
                if(methodType != "POST"){
                    httpClient .GetAsync(Arg.Any<Uri>()).ReturnsForAnyArgs(httpResponse);
                }
                return httpClient 
            }
James Law
  • 6,067
  • 4
  • 36
  • 49
superninja
  • 3,114
  • 7
  • 30
  • 63
  • 2
    all the calls GET POST etc invoke the `Send` method. I would however suggest no mocking httpclient. use an actual httpclient. mock the handler (which the client calls) and let it handle the request. – Nkosi Aug 07 '21 at 02:09
  • Have a look at this answer https://stackoverflow.com/a/36427274/5233410 – Nkosi Aug 07 '21 at 02:15
  • @Nkosi thanks, I have tried it without using NSubstitute but I got the same error – superninja Aug 09 '21 at 19:18
  • Try adding the [NSubstitute.Analyzers](https://nsubstitute.github.io/help/nsubstitute-analysers/) package to your test project. It might be able to pick up the cause of this. – David Tchepak Aug 10 '21 at 23:36

1 Answers1

14

I appreciate this is an old(ish) question, but it's at the top of the Google Search results for "c# mock httpclient using nsubstitute" today, so I figured an answer would be useful.

First we need to create a mock implemenation of HttpMessageHandler. As you can see, we're overriding the protected SendAsync() method and exposing its body via our public Send() method.

public class MockHttpMessageHandler : HttpMessageHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Send(request, cancellationToken);
    }

    public virtual Task<HttpResponseMessage> Send(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }
}

Next we need to set up our mocks. Note that I'm using Substitute.ForPartsOf<T> instead of Substitute.For<T>.

var mockHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
var httpClient = new HttpClient(mockHttpMessageHandler);

Finally, we can now use NSubstitute to intercept the call to Send() on our handler, which is called by the HttpClient for every request, and return our mocked HttpResponseMessage back via the client.

var mockResponse = new HttpResponseMessage(HttpStatusCode.OK);

mockHttpMessageHandler.Send(Arg.Any<AnyHttpRequestMessage>(), Arg.Any<CancellationToken>())
   .Returns(mockResponse);

var result = await httpClient.GetAsync<string>("https://tempuri.org");

Edit for .NET 6

As .NET 6 introduces a protected virtual Send() method to the HttpMessageHandler class (which will also need overriding if you're using the synchronous HttpClient calls), some modifications are required to our MockHttpMessageHandler:

public class MockHttpMessageHandler : HttpMessageHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Task.FromResult(MockSend(request, cancellationToken));
    }

    protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return MockSend(request, cancellationToken);
    }

    public virtual HttpResponseMessage MockSend(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }
}
James Law
  • 6,067
  • 4
  • 36
  • 49
  • 2
    In dotnet 6 there is now a `protected virtual Send()` method so this no longer works as written. – ChrisWue Aug 01 '22 at 01:17
  • As this is written it does not work: We get a "access protection" error for the mockHttpMessageHandler.Send !! If we use mockHttpMessageHandler.MockSend , we get "Can not return value of type Task`1 for MockHttpMessageHandler.MockSend (expected..." – GregJF Mar 14 '23 at 05:31
  • @GregJF it sounds like you're attempting to call the `MockHttpMessageHandler` directly rather than using it in conjunction with a `HttpClient` instance. – James Law Mar 15 '23 at 10:43