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!)

4
  • 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. Commented Mar 6, 2014 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? Commented Mar 6, 2014 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). Commented Mar 6, 2014 at 12:08
  • In that case see Non Interface dependent Mocking Frameworks for C#, which I found through ".net mock not virtual methods without interface". The accepted answer advises to use .NET Moles, the only .NET mocking framework I know of that works without virtual methods. Commented Mar 6, 2014 at 12:09

4 Answers 4

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); 
Sign up to request clarification or add additional context in comments.

9 Comments

Nice solution to an interesting problem. Literally does everything except send HTTP over the wire.
Your example will not compile as it is. You need to using Task.FromResult to return your HttpResponseMessage instances.
@Ananke The async keyword on the SendAsync method does that magic for you.
Does anyone else think this is much more unnecessarily complicated than having an IHttpClient interface to mock?
Turns out POSTs call to DelegatingHandler.SendAsync despite the method name: stackoverflow.com/questions/29106664/…
|
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.

Comments

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 }); } } } 

2 Comments

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.
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);
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.

1 Comment

I don't have access to those two expensive editions.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.