I am writing some tests where the class I'm testing depends on HttpClient
. To mock that I am mocking a HttpMessageHandler
and pass that to the HttpClient
constructor.
To accomplish this I have a base class:
public class HttpTestBase
{
protected static readonly string BaseAddress = "https://test.com";
protected readonly HttpClient _httpClient;
protected readonly Mock<HttpMessageHandler> _httpMessageHandlerMock;
public HttpTestBase()
{
_httpMessageHandlerMock = new Mock<HttpMessageHandler>();
_httpClient = new HttpClient(_httpMessageHandlerMock.Object);
_httpClient.BaseAddress = new Uri(BaseAddress);
}
protected void MockHttpResponse(HttpResponseMessage message, string expectedPath, HttpMethod expectedMethod)
{
_httpMessageHandlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(request => AssertRequestParameters(expectedPath, expectedMethod, request)),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(message);
}
private bool AssertRequestParameters(string expectedPath, HttpMethod expectedMethod, HttpRequestMessage request)
{
// Throw an exception if the method or path does not match what is expected.
}
}
And then a test looks as follows, in a test class inheriting this test base:
[Fact]
public async Task GetAvailableLicenseCount()
{
// Arrange
var licenses = new JsonObject
{
["total_seats_consumed"] = 4500,
["total_seats_purchased"] = 5000
};
MockHttpResponse(
new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(licenses.ToJsonString()) },
expectedPath: "/enterprises/enterprise/consumed-licenses",
expectedMethod: HttpMethod.Get
);
// Act
var result = await sut.GetAvailableLicenseCount();
// Assert
result.Should().Be(500);
}
This works well when a method only performs a single call. Some methods perform multiple calls in a sequence. I have solved that partially, but I would still like to have the assertion in the setup (eg when I call AssertRequestParameters
).
To support multiple calls in a chain, I instead did this in the base class:
protected void AddHttpMockResponse(HttpResponseMessage message, string expectedPath, HttpMethod expectedMethod)
{
_responseMocks.Enqueue(new HttpMock
{
Response = message,
Assertion = new Assertion { ExpectedPath = expectedPath, ExpectedMethod = expectedMethod }
});
}
protected void MockHttpResponses()
{
_httpMessageHandlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(() => _responseMocks.Dequeue().Response);
}
As you can see I have removed the assertion in the setup, because it seems that the assertions collide and I get the wrong return value from the mock.
To use this, I refactored the test as follows:
[Fact]
public async Task GetAvailableLicenseCount()
{
// Arrange
var licenses = new JsonObject
{
["total_seats_consumed"] = 4500,
["total_seats_purchased"] = 5000
};
AddHttpMockResponse(
new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(licenses.ToJsonString()) },
expectedPath: "/enterprises/enterprise/consumed-licenses",
expectedMethod: HttpMethod.Get
);
MockHttpResponses();
// Act
var result = await sut.GetAvailableLicenseCount();
// Assert
result.Should().Be(500);
}
This also works well for multiple responses from the same mock.
However, I would like to be able to mock multiple responses where the response is determined by what I use in the Setup
of the mock. The reason I want to do this is that I cannot use Verify
on HttpMessageHandler
as the method is not accessible to me .
Is it possible to achieve a dynamic return value based on what is done in the setup of the mock?
If I understand your question correctly you'll want something like this:
I recently had to do a similar thing and found the below article very helpful. Check the 'Mock HttpMessageHandler Using Moq' section. It shows you how to return a HttpResponseMessage
object you create, which is what I think you might want.
https://code-maze.com/csharp-mock-httpclient-with-unit-tests/
Field on your class
private readonly Mock<HttpMessageHandler> _httpMessageHandlerStub;
In your setup (constructor or a separate method)
_httpMessageHandlerStub = new Mock<HttpMessageHandler>();
var httpClient = new HttpClient(_httpMessageHandlerStub.Object);
_sut = new SomeService(
httpClient,
anotherDependency
)
And then you can configure the below in each test
[Fact]
public async Task GetListAsync_Successfully_Uses_Api_Key()
{
var unauthorisedResponseMessage = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized)
{
Content = new StringContent($"'{_apiKeyHeaderName}' header not found or API key is incorrect")
};
var successResponseMessage = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent(JsonConvert.SerializeObject(new List<SomeDto>()))
};
// Return 200 if api key header is present and the value is correct.
_httpMessageHandlerStub.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(x => x.Method == HttpMethod.Get && x.Headers.Any(h => h.Key == _apiKeyHeaderName && h.Value.FirstOrDefault() == _apiKeyHeaderValue)),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(successResponseMessage);
// Return 401 is api key header is not present or api key value is incorrect.
_httpMessageHandlerStub.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(x => x.Method == HttpMethod.Get && !x.Headers.Any(h => h.Key == _apiKeyHeaderName && h.Value.FirstOrDefault() == _apiKeyHeaderValue)),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(unauthorisedResponseMessage);
var result = _sut.GetListAsync();
// Assert stuff...
}
Hope that's helpful.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.