Using WireMock in integration tests

May 31st 2024 Unit Testing Moq .NET

When testing code that depends on HTTP calls, it's most common to mock the unit of the code that does the HTTP calls. In integration tests, however, you don't want to modify the application code. So you need to mock the server responding to the HTTP calls. WireMock.Net is a convenient tool to do that directly from test code.

In a typical .NET application, HttpClient calls are wrapped in a service class which is then called from other application code:

HTTP dependencies in code

This makes it easy to abstract away the HTTP dependency when testing any other application code by mocking the service class:

[Test]
public async Task MockApiClient()
{
    var apiClient = new Mock<IApiClient>();
    apiClient
        .Setup(x => x.GetTopStoriesAsync(It.IsAny<Uri>()))
        .ReturnsAsync(sampleTopStories);
    var appCommand = new AppCommand(apiClient.Object)
    {
        BaseUrl = new Uri("https://example.com")
    };

    var result = await appCommand.OnExecuteAsync();

    result.Should().Be(0);
}

To test the code you might have in the HTTP client wrapper, specific HTTP responses need to be simulated. This can best be done by mocking the HtppClient. I wrote about this in more detail in a previous blog post. I find it the simplest to use the Moq.Contrib.HttpClient NuGet package:

[Test]
public async Task MockHttpClient()
{
    var baseUrl = new Uri("https://hacker-news.firebaseio.com");
    var handlerMock = new Mock<HttpMessageHandler>();
    handlerMock
        .SetupRequest(HttpMethod.Get, $"{baseUrl}v0/topstories.json")
        .ReturnsJsonResponse(sampleTopStories);
    var httpClient = handlerMock.CreateClient();
    var apiClient = new ApiClient(httpClient);

    var topStories = await apiClient.GetTopStoriesAsync(baseUrl);

    topStories.Should().BeEquivalentTo(sampleTopStories);
}

In addition to unit tests, you might want to write a couple of integration tests for your application to test it as a whole without mocking any of its code. At least, to make sure that you have all the dependencies registered correctly. To avoid external dependencies for these tests, you need to mock the servers being called. As long as you have the base URLs of those servers configurable, you can mock them using WireMock.Net:

[Test]
public void MockApi()
{
    var server = WireMockServer.Start();
    try
    {
        server
            .Given(Request.Create().WithPath("/v0/topstories.json"))
            .RespondWith(Response.Create().WithBodyAsJson(sampleTopStories));
        var entryPoint = typeof(AppCommand).Assembly.EntryPoint!;
        string[] args = ["--base-url", server.Urls[0]];

        var result = entryPoint.Invoke(null, [args]);

        result.Should().Be(0);
    }
    finally
    {
        server.Stop();
    }
}

The code is not much different from when you are mocking the HttpClient: you still specify responses for each request. But the mocking happens at a different level: WireMock.Net creates an actual web server on the local machine which responds to HTTP requests issued by the application under test. The lifetime of the web server is controlled directly from the tests with the Start and Stop calls.

In my case the application under test is a console application. I described the approach I'm using for invoking it in more details in a previous blog post.

A sample project with full source code is available in my GitHub repository. It contains a simple console application with tests mocking the HTTP call at all 3 levels described in this post.

Integration tests can be challenging to write, but at least the aspect of mocking the external HTTP dependencies can be taken care of with WireMock.Net. With a minimum amount of code, you can respond to any URL on a given address and verify the calls being made. The example in this post is only a small taste of what it can do. Check the official documentation to learn more about it.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License