Is there are way to verify the request object passed to HttpClient.PostAsJsonAsync
. The request object is constructed inside the method to be unit tested. Therefore I need to verify that the request object is constructed with correct values.
I tried the following and I am expecting to verify the searchrequest object by comparing the values as shown below
public async Task Test()
{
//Arrange
var handlerMock = new Mock<HttpClientHandler>();
handlerMock.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("{'foo':'bar'}")
});
httpClient = new HttpClient(handlerMock.Object);
//ACT
var criteria = new Criteria();
await SomeMethodToTestAsync(criteria);
//Assert
var publishedDateStart = new DateOnly(2021, 10, 17);
handlerMock.Protected().Verify(
"SendAsync",
Times.Exactly(1), // we expected a single external request
ItExpr.Is<HttpRequestMessage>(req =>
req.Method == HttpMethod.Post // we expected a POST request
),
ItExpr.IsAny<CancellationToken>(),
ItExpr.Is<SearchRequest>(r => r.PublishedDateStart == publishedDateStart) // this doesn't work
);
}
To be tested method
public async Task SomeMethodToTestAsync(Criteria criteria)
{
var url = "https://example.com";
// build some complex request from criteria
var searchRequest = new SearchRequest
{
PublishedDateStart = new DateOnly(2021, 10, 17)
};
var searchResponse = await httpClient.PostAsJsonAsync(url, searchRequest);
//other code
}
2
Answers
The
PostAsJsonAsync
extension method doesn’t pass aSearchRequest
toSendAsync
. It serializes theSearchRequest
to JSON and packages that string in some kind ofHttpContent
object. This means that when verifying theSendAsync
call, the test must inspect theHttpRequestMessage
that thePostAsJsonAsync
method generated.While really not recommended you can achieve that goal by changing the
Verify
call to something like this:While this passes the test, it’s really not recommended:
ItExpr.Is
predicates need to run synchronously, which means that you need to useResult
to pull the JSON string out of theContent
. That’s not recommended, as it may cause deadlocks.If the implementation of
PostAsJsonAsync
or the internal workings ofHttpClient
changes when you update dependencies, this could easily break tests like this one. You’d end up in a frustrating game of Library Whac-A-Mole.Fragile Tests are generally associated with interaction-based tests. Consider state-based testing instead. Concretely, instead of using Moq at all, you may consider implementing a test-specific Fake
HttpClientHandler
.Another option is to stand up a self-hosted service and test against that.
Making an assessment against the request body is a bit clumsy IMHO, because you can do that inside the
ReturnsAsync
.ReturnsAsync
has an overload which can be used to dynamically compute the return valueHttpRequestMessage
and aCancellationToken
PostAsJsonAsync
uses aJsonContent
to represent the request bodyJsonContent
has aValue
property which allows you to access the original (not the serialized) objectSo, here (inside the
ReturnsAsync
) you can perform your assessment regarding to the request body without the need to do string comparison.Dotnet fiddle: https://dotnetfiddle.net/FYiqAk