In my Web API app, I'm using HttpServer
to contain my controller in unit tests, and I'm using HttpClient
to call it directly, eg:
[Fact]
public void TestMyController()
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute("default", "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional });
var server = new HttpServer(config);
var client = new HttpClient(server);
var response = client.GetAsync("http://localhost/api/test/values").Result;
}
I've noticed (by stepping through the debugger, and confirmed on other SO posts), that the JsonFormatter
is not really running - it's initialized, but not exercised. Since this test isn't opening a socket, and the HttpClient
is directly invoking the HttpServer
through the HttpMessageHandler
API, it does make sense that formatting/serialization isn't run because it's not needed.
In my case, I have some custom formatting/serialization/deserialization code that isn't being hit during these tests, but it's hit when I run in a real web server. I'd like to exercise that code in these tests; and it also just seems risky to exclude the serialization/deserialization code path when testing. Any advice on this?
Following is a quick example of what you could do to force formatters to go through serialization/deserialization. Here we are converting ObjectContent
to StreamContent
. In the below code, the call to CopyToAsync
triggers a path where formatters are forced to serialize. In case of deserilization, in order to make sure we go through formatters we want the content to be of type other than ObjectContent
as ReadAsAsync
has internal logic which special cases ObjectContnent
and we want to circumvent it.
HttpClient client = new HttpClient(new InMemoryHttpContentSerializationHandler(new HttpServer(config)));
public class InMemoryHttpContentSerializationHandler : DelegatingHandler
{
public InMemoryHttpContentSerializationHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Content = await ConvertToStreamContentAsync(request.Content);
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
response.Content = await ConvertToStreamContentAsync(response.Content);
return response;
}
private async Task<StreamContent> ConvertToStreamContentAsync(HttpContent originalContent)
{
if (originalContent == null)
{
return null;
}
StreamContent streamContent = originalContent as StreamContent;
if (streamContent != null)
{
return streamContent;
}
MemoryStream ms = new MemoryStream();
await originalContent.CopyToAsync(ms);
// Reset the stream position back to 0 as in the previous CopyToAsync() call,
// a formatter for example, could have made the position to be at the end
ms.Position = 0;
streamContent = new StreamContent(ms);
// copy headers from the original content
foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers)
{
streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
return streamContent;
}
}