56

I have a service which uses Microsoft.Net.Http to retrieve some Json data. Great!

Of course, I don't want my unit test hitting the actual server (otherwise, that's an integration test).

Here's my service ctor (which uses dependency injection...)

public Foo(string name, HttpClient httpClient = null)
{
...
}

I'm not sure how I can mock this with ... say .. Moq or FakeItEasy.

I want to make sure that when my service calls GetAsync or PostAsync .. then i can fake those calls.

Any suggestions how I can do that?

I'm -hoping- i don't need to make my own Wrapper .. cause that's crap :( Microsoft can't have made an oversight with this, right?

(yes, it's easy to make wrappers .. i've done them before ... but it's the point!)

Darrel Miller
  • 139,164
  • 32
  • 194
  • 243
Pure.Krome
  • 84,693
  • 113
  • 396
  • 647
  • 1
    _"I'm -hoping- i don't need to make my own Wrapper"_ - how many of the `HttpClient`'s methods and properties do you use? It can usually prove useful to create an interface for those, which you can then mock. – CodeCaster Mar 06 '14 at 11:56
  • (Like I mentioned in the OP) Agreed - it's easy and simple ... but I would have though that I shouldn't have to? This is something that should be core .. right? Is this my only option? – Pure.Krome Mar 06 '14 at 12:06
  • 1
    As far as i know you could stub those calls if they would be virtual. As they aren't, I'd assume you need to write a wrapper (which is the cleaner way imo). – ElGauchooo Mar 06 '14 at 12:08
  • In that case see [Non Interface dependent Mocking Frameworks for C#](http://stackoverflow.com/questions/3230225/non-interface-dependent-mocking-frameworks-for-c-sharp), which I found through _".net mock not virtual methods without interface"_. The accepted answer advises to use [.NET Moles](http://research.microsoft.com/en-us/projects/moles/), the only .NET mocking framework I know of that works without virtual methods. – CodeCaster Mar 06 '14 at 12:09

4 Answers4

96

You can replace the core HttpMessageHandler with a fake one. Something that looks like this...

public class FakeResponseHandler : DelegatingHandler
{
    private readonly Dictionary<Uri, HttpResponseMessage> _FakeResponses = new Dictionary<Uri, HttpResponseMessage>();

    public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage)
    {
        _FakeResponses.Add(uri, responseMessage);
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        if (_FakeResponses.ContainsKey(request.RequestUri))
        {
            return Task.FromResult(_FakeResponses[request.RequestUri]);
        }
        else
        {
            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request });
        }
    }
}

and then you can create a client that will use the fake handler.

var fakeResponseHandler = new FakeResponseHandler();
fakeResponseHandler.AddFakeResponse(new Uri("http://example.org/test"), new HttpResponseMessage(HttpStatusCode.OK));

var httpClient = new HttpClient(fakeResponseHandler);

var response1 = await httpClient.GetAsync("http://example.org/notthere");
var response2 = await httpClient.GetAsync("http://example.org/test");

Assert.Equal(response1.StatusCode,HttpStatusCode.NotFound);
Assert.Equal(response2.StatusCode, HttpStatusCode.OK);
kendaleiv
  • 5,793
  • 3
  • 28
  • 38
Darrel Miller
  • 139,164
  • 32
  • 194
  • 243
  • 1
    Nice solution to an interesting problem. Literally does everything except send HTTP over the wire. – Spence Mar 10 '15 at 05:41
  • 1
    Your example will not compile as it is. You need to using Task.FromResult to return your HttpResponseMessage instances. – Ananke Jun 22 '15 at 15:21
  • 1
    @Ananke The async keyword on the SendAsync method does that magic for you. – Darrel Miller Jun 22 '15 at 18:46
  • I like it! Can I suggest a small improvement? In the SendAsync method where you return the value from the dictionary, I made it so that you attach the original request as well in case you want to inspect that, like so: var response = _FakeResponses[request.RequestUri]; response.RequestMessage = request; return response; Doing this allowed this solution to meet my needs nicely. Thanks! – Craig Brett Jan 14 '16 at 14:21
  • @CraigBrett Yup, that makes sense – Darrel Miller Jan 14 '16 at 20:02
  • 1
    Does anyone else think this is much more unnecessarily complicated than having an `IHttpClient` interface to mock? – Jeremy Holovacs Jun 28 '16 at 20:43
  • How would you handle POSTs and PUTs with this method? – U Avalos Aug 02 '16 at 03:59
  • 1
    Turns out POSTs call to DelegatingHandler.SendAsync despite the method name: http://stackoverflow.com/questions/29106664/intercept-c-sharp-httpclient-getasync – jgerman Oct 25 '16 at 18:49
  • If you are looking for a more complete version of this we created a ChaosHandler here https://github.com/microsoftgraph/msgraph-sdk-dotnet-core/blob/dev/src/Microsoft.Graph.Core/Requests/Middleware/ChaosHandler.cs that can also inject random failures. – Darrel Miller Mar 14 '21 at 03:33
19

I know that this is an old question but I stumbled with it during a search on this topic and found a very nice solution to make testing HttpClient easier.

It is available via nuget:

https://github.com/richardszalay/mockhttp

PM> Install-Package RichardSzalay.MockHttp

Here is a quick look on the usage:

var mockHttp = new MockHttpMessageHandler();

// Setup a respond for the user api (including a wildcard in the URL)
mockHttp.When("http://localost/api/user/*")
        .Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON

// Inject the handler or client into your application code
var client = new HttpClient(mockHttp);

var response = await client.GetAsync("http://localost/api/user/1234");
// or without await: var response = client.GetAsync("http://localost/api/user/1234").Result;

var json = await response.Content.ReadAsStringAsync();

// No network connection required
Console.Write(json); // {'name' : 'Test McGee'}

More info on the github project page. Hope this can be useful.

Fedaykin
  • 4,482
  • 3
  • 22
  • 32
2

I would just make a small change to @Darrel Miller's answer, which is using Task.FromResult to avoid the warning about an async method expecting an await operator.

public class FakeResponseHandler : DelegatingHandler
{
    private readonly Dictionary<Uri, HttpResponseMessage> _FakeResponses = new Dictionary<Uri, HttpResponseMessage>();

    public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage)
    {
        _FakeResponses.Add(uri, responseMessage);
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        if (_FakeResponses.ContainsKey(request.RequestUri))
        {
            return Task.FromResult(_FakeResponses[request.RequestUri]);
        }
        else
        {
            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request });
        }
    }
}
Community
  • 1
  • 1
Rui Taborda
  • 170
  • 1
  • 3
  • 17
  • 4
    I would be cautious using that technique in any environment that is performance sensitive as it could cause a thread switch for no benefit. If you are really concerned about the warning, then just remove the async keyword and use Task.FromResult. – Darrel Miller Oct 01 '15 at 20:12
  • 5
    If the implementation is not task based it is better to return Task.FromResult and remove the async keyword instead of using Task.Run. return Task.FromResult(response); – user3285954 Nov 07 '15 at 15:41
1

You might take a look at Microsoft Fakes, especially at the Shims-section. With them, you're able to modify the behaviours of your HttpClient itself. Prerequisite is, that you're using VS Premium or Ultimate.

ElGauchooo
  • 4,256
  • 2
  • 13
  • 16