This is how you can unit test your methods that use HttpClient with Moq and xUnit.
We don't want our unit tests to actually perform HTTP requests during testing so we will have to mock those requests. Moq allows us to mock overridable members such as abstract, virtual, or interface methods. But this doesn't exist in HttpClient. We could wrap HttpClient in an Interface, but that would result in extra implementation code and we don't want to alter implementation code to support tests. But if we look at the constructor, it takes in a HttpMessageHandler that contains an abstract SendAsync method that is used by HttpClient. This is what we want to mock!
Note that in HttpClient all GetAsync
, PostAsync
, PatchAsync
, PutAsync
, DeleteAsync
, and SendAsync
use the SendAsync method in the HttpMessageHandler internally and can be mocked.
Implementation to test
Here is an example of a Posts class which can fetch posts and create a post.
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.Json;
namespace HttpCode
{
public class Posts
{
private readonly HttpClient httpClient;
private const string url = "https://jsonplaceholder.typicode.com/posts";
public Posts(HttpClient httpClient)
{
this.httpClient = httpClient;
}
public async Task<IEnumerable<JsonElement>> GetPosts()
{
var response = await httpClient.GetAsync(url);
var body = await response.Content.ReadAsStringAsync();
var posts = JsonSerializer.Deserialize<IEnumerable<JsonElement>>(body);
return posts;
}
public async Task<JsonElement> CreatePost(string title)
{
var payload = new
{
title
};
var httpContent = new StringContent(JsonSerializer.Serialize(payload));
var response = await httpClient.PostAsync(url, httpContent);
var body = await response.Content.ReadAsStringAsync();
var created = JsonSerializer.Deserialize<JsonElement>(body);
return created;
}
}
}
Unit Tests
Now let's mock the SendAsync method to test our two methods. The SendAsync method is protected, so we need to use .Protected()
and access the method with a string to be able to mock it.
using HttpCode;
using Moq;
using Moq.Protected;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace HttpCodeTests
{
public class PostsTest
{
[Fact]
public async void ShouldReturnPosts()
{
var handlerMock = new Mock<HttpMessageHandler>();
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(@"[{ ""id"": 1, ""title"": ""Cool post!""}, { ""id"": 100, ""title"": ""Some title""}]"),
};
handlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(response);
var httpClient = new HttpClient(handlerMock.Object);
var posts = new Posts(httpClient);
var retrievedPosts = await posts.GetPosts();
Assert.NotNull(retrievedPosts);
handlerMock.Protected().Verify(
"SendAsync",
Times.Exactly(1),
ItExpr.Is<HttpRequestMessage>(req => req.Method == HttpMethod.Get),
ItExpr.IsAny<CancellationToken>());
}
[Fact]
public async void ShouldCallCreatePostApi()
{
var handlerMock = new Mock<HttpMessageHandler>();
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(@"{ ""id"": 101 }"),
};
handlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(response);
var httpClient = new HttpClient(handlerMock.Object);
var posts = new Posts(httpClient);
var retrievedPosts = await posts.CreatePost("Best post");
handlerMock.Protected().Verify(
"SendAsync",
Times.Exactly(1),
ItExpr.Is<HttpRequestMessage>(req => req.Method == HttpMethod.Post),
ItExpr.IsAny<CancellationToken>());
}
}
}
That's All
Hope this can help you as well. Happy coding!π¨βπ»π©βπ»
Top comments (5)
Hi, thank you very much for the tutorial ! It helps me to know how to test the SendAsync function.
However, I'm little confused with the assert action. Usually we will compare the unit test result with our expectation result, and we put inside the "assert" part. In your example, where do you put the assert? Thanks :)
Hi thanks for the comment :)
In the first test you see I do the assert
Assert.NotNull(retrievedPosts);
, so it's there I would have it. Because my demo codeGetPosts
andCreatePost
is simple it doesn't feel natural to add more assertions. But maybe one I could have added wasAssert.Equal(2, retrievedPosts.Count);
instead ofAssert.NotNull(retrievedPosts);
and there is where I would put it.Thank you, this helped me move forward in my unit testing :)
Thanks for the explanation. It helped me create a test which verifies custom headers and apiUrl. Wondering if you can add how to verify req.content would be great.
Thank you, helped me a lot!