This article will cover all the bases when it comes to creating and managing mock APIs for integration testing with WireMock.NET.

To download the source code for this article, you can visit our GitHub repository.

Let’s jump in!

What Is Integration Testing and Why Is It Important?

Integration testing ensures smooth communication between different components of a software system, helping eliminate any issues that may arise. It plays a crucial role in testing how our applications interact with both internal and external APIs, confirming compliance with predefined contracts and desired functionality.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

WireMock.NET is a comprehensive package that mimics HTTP API behavior, making it ideal for simulating APIs in various scenarios, such as testing classes dependent on APIs or performing integration tests with external APIs.

For a detailed exploration of integration testing in ASP.NET Core check out our article Integration Testing in ASP.NET Core.

Setting up Our Integration Testing Example

Imagine we are tasked with building a service that interacts with an API that is still in development. We are given only an endpoint – planets/{id} and a contract class:

public class Planet
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double Diameter { get; set; }
    public int NumberOfMoons { get; set; }
    public bool HasAtmosphere { get; set; }

    public Planet(int id, string name, double diameter, int numberOfMoons, bool hasAtmosphere)
    {
        Id = id;
        Name = name;
        Diameter = diameter;
        NumberOfMoons = numberOfMoons;
        HasAtmosphere = hasAtmosphere;
    }
}

The contract defines a Planet class with properties representing the Id, Name, Diameter, NumberOfMoons, and HasAtmosphere for a given planet. It also includes a constructor to initialize these properties when creating a new Planet object.

Having this information we create the service:

public class PlanetsService
{
    private readonly HttpClient _httpClient;

    public PlanetsService(HttpClient httpClient)
    {
        _httpClient = httpClient ??
            throw new ArgumentNullException(nameof(httpClient));
    }

    public async Task<Planet?> GetPlanetByIdAsync(int id)
    {
        var response = await _httpClient.GetAsync($"planets/{id}");

        if (response.StatusCode == HttpStatusCode.OK)
        {
            return JsonSerializer.Deserialize<Planet>(await response.Content.ReadAsStringAsync());
        }

        return null;
    }
}

The PlanetsService class has one GetPlanetByIdAsync method that uses an HttpClient to send an HTTP GET request to retrieve information about a planet with a specified ID. It returns the deserialized Planet object if the request is successful, or null otherwise.

In the next sections, we will see how WireMock.NET can help us with the testing of our service.

Installing WireMock.NET

First, we need to install the WireMock.Net NuGet package.

Let’s do this via the .NET CLI:

dotnet add package WireMock.Net

Once this is done, we can look and how the package works and how we can use it.

Understanding How WireMock.NET Works

WireMock.NET is a powerful tool that can accurately emulate the behavior of an HTTP API by capturing incoming requests and directing them to a WireMock.NET HTTP server. This capability grants us the ability to define expectations, call the API, and verify its behavior, making it ideal for extensive testing of code interactions with external services.

With WireMock.NET, we can effectively mock real API endpoints and utilize its comprehensive features for HTTP response stubbing, including matching stubs based on URL/Path, headers, cookies, and body content patterns.

Creating Mock APIs for Integration Testing With WireMock.NET

To test our service we need a test project, and for this, we opt for xUnit:

public class PlanetsServiceTests : IDisposable
{
    private readonly HttpClient _httpClient;
    private readonly WireMockServer _mockServer;
    private readonly PlanetsService _planetService;

    public PlanetsServiceTests()
    {
        _mockServer = WireMockServer.Start();

        _httpClient = new HttpClient
        {
            BaseAddress = new Uri(_mockServer.Urls[0])
        };

        _planetService = new PlanetsService(_httpClient);
    }

    public void Dispose()
    {
        _mockServer.Stop();
        _httpClient.Dispose();
    }
}

We define a PlanetsServiceTests class that implements the IDisposable interface. We set up an instance of a mock server using the WireMockServer.Start method. Next, we create an instance of HttpClient with the base address set to the first URL of our _mockServer field. We move on to initialize an instance of PlanetsService passing the _httpClient as a parameter. Finally, we implement the Dispose method to stop the mock server and dispose of the HttpClient when the instance is no longer needed.

Mocking an HTTP Request and an OK Response With WireMock.NET

We can use WireMock.NET to simulate a successful HTTP response and body from a server. To that, we create a test that verifies the behavior of the GetPlanetByIdAsync method in the PlanetsService class.

First, we set up the necessary arrangements and conditions:

var planet = new Planet(4, "Mars", 6779, 2, true);

We create a Planet object representing the planet Mars.

Next, we configure an instance of the WireMockServer:

_mockServer
    .Given(
        Request.Create()
            .UsingGet()
            .WithPath("/planets/4"))
    .RespondWith(
        Response.Create()
            .WithStatusCode(HttpStatusCode.OK)
            .WithBodyAsJson(planet));

In the Given method we start with creating the request we expect. To do this, we use the Request.Create method. Then we state that we expect a GET request to the /planets/4 endpoint. Then in the RespondWith of the _mockServer instance we specify the response to be returned. In this case, it responds with a JSON representation of the planet object and a status code of HttpStatusCode.OK indicating that the response was a success.

After the arrangement, we proceed with the Act part of our test:
var result = await _planetService.GetPlanetByIdAsync(planet.Id);

We call the GetPlanetByIdAsync method of the _planetService instance, passing in the ID of the planet object created earlier.

Finally, we assert the expected behavior and examine the final method:

[Fact]
public async Task GivenThatPlanetExists_WhenGetPlanetByIdIsInvoked_ThenValidPlanetIsReturned()
{
    // Arrange
    var planet = new Planet(4, "Mars", 6779, 2, true);

    _mockServer
        .Given(
            Request.Create()
                .UsingGet()
                .WithPath("/planets/4"))
        .RespondWith(
            Response.Create()
                .WithStatusCode(HttpStatusCode.OK)
                .WithBodyAsJson(planet));

    // Act
    var result = await _planetService.GetPlanetByIdAsync(planet.Id);

    // Assert
    result.Should().NotBeNull();
    result.Name.Should().Be(planet.Name);
    result.Diameter.Should().Be(planet.Diameter);
    result.NumberOfMoons.Should().Be(planet.NumberOfMoons);
    result.HasAtmosphere.Should().Be(planet.HasAtmosphere);
}

We do the assertions using the FluentAssertions library and check that everything with the returned Planet object is identical to what we expect to get.

Mocking an HTTP Request and a NotFound Response With WireMock.NET

WireMock.NET can be used to mock other HTTP responses as well:

[Fact]
public async Task GivenThatPlanetDoesntExists_WhenGetPlanetIsInvoked_ThenNullIsReturned()
{
    // Arrange
    _mockServer
        .Given(
            Request.Create()
            .UsingGet()
            .WithPath("/planets/9"))
        .RespondWith(
            Response.Create()
            .WithStatusCode(HttpStatusCode.NotFound));

    // Act
    var result = await _planetService.GetPlanetByIdAsync(9);

    // Assert
    result.Should().BeNull();
}

We start by configuring the _mockServer. We state that we expect a GET request to the path /planets/9 (representing the ID of a non-existent planet). Then, we specify the response that we want the server to return. Here, the response is set to have a status code of HttpStatusCode.NotFound, indicating that the requested resource was not found. After this, we move on to call the GetPlanetByIdAsync method of the _planetService instance. This time we pass in an ID of a planet that doesn’t exist. Finally, we verify that the returned result should be null.

Different Ways to Enhance Integration Tests With WireMock.NET

WireMock.NET has a variety of settings and methods that can help us enhance our integration tests. The package also provides us with ample ways to fine-tune our mock server in various ways. 

Defining Custom URLs and SSL for the WireMock.NET Server

We have to possibility to initialize the client with custom URLs:

var server = WireMockServer.Start(new WireMockServerSettings
{
    Urls = new[] {
        "http://localhost:7777/",
        "http://localhost:8888/",
        "http://localhost:9999/"
    },
    UseSSL = true
});

We initialize and start a WireMockServer instance with specific URL settings. The server will be listening on three different URLs: http://localhost:7777/, http://localhost:8888/, and http://localhost:9999/. These URLs represent the endpoints where the mock server will simulate the behavior of external services or dependencies during our integration testing. Additionally, we enable SSL (Secure Sockets Layer) by setting the UseSSL property to true, ensuring that the server supports secure communication over HTTPS.

Delayed Responses For Integration Testing With WireMock.NET

Like any real-life API, we can configure delays for our mock server:

server
    .Given(
        Request.Create()
            .UsingGet()
            .WithPath("/planets/9"))
    .RespondWith(
        Response.Create()
            .WithStatusCode(HttpStatusCode.NotFound)
            .WithDelay(TimeSpan.FromMilliseconds(500)));

Using our configuration from the HttpStatusCode.NotFound example, we update the response by adding a half-second delay. To do this we use the WithDelay method and pass TimeSpan.FromMilliseconds(500).

We can also have a random delay:

server
    .Given(
        Request.Create()
            .UsingGet()
            .WithPath("/planets/9"))
    .RespondWith(
        Response.Create()
            .WithStatusCode(HttpStatusCode.NotFound)
            .WithRandomDelay());

WireMock.NET provides the WithRandomDelay method. By default, it will delay the response with a value between 0 and 60000 milliseconds, but we can pass our preferences as well.

Simulating Faults For Integration Testing With WireMock.NET

One key benefit of WireMock.NET it allows us to test the stability of your system against faulty or unforeseen behavior. One way to do this is by returning faulty responses:

server
    .Given(
        Request.Create()
            .UsingGet()
            .WithPath("/planets/3"))
    .RespondWith(
        Response.Create()
            .WithFault(FaultType.MALFORMED_RESPONSE_CHUNK));

We set the server to expect a GET request with the path /planets/3 and responds with a fault of type MALFORMED_RESPONSE_CHUNK. With this configuration we simulate a scenario where the server intentionally sends a response with a malformed response chunk, allowing testing of error handling or resilience mechanisms in our code. We have one more fault type at our disposal – EMPTY_RESPONSE. Just as the name suggests, this will return an empty response from the server.

To take this one step further, the WithFault method has an optional percentage parameter of double type. By passing a value between 0 and 1 we set how likely is that the server will return a faulty response, gaining a level of randomness.

Custom Header, Wildcards, and Priority Request Matching for Integration Testing With WireMock.NET

We can set up our server to respond only if a particular header is present:

server
    .Given(
        Request.Create()
            .UsingGet()
            .WithPath("/planets/4")
            .WithHeader("AwesomeRedHeader", "MarsIsAwesome"))
    .RespondWith(
        Response.Create()
            .WithSuccess()
            .WithBody("The Red Planet is responding to the awesome header"));

We specify that when a GET request is made to the path /planets/4 with a header AwesomeRedHeader having the value MarsIsAwesome, the server should respond with a successful status code and a body containing the message The Red Planet is responding to the awesome header

WireMock.NET also has functionality for wildcards and priority matching:

server
    .Given(
        Request.Create()
            .UsingPost()
            .WithPath("/planets/*"))
    .AtPriority(5)
    .RespondWith(
        Response.Create()
            .WithStatusCode(HttpStatusCode.Unauthorized));

server
    .Given(
        Request.Create()
        .UsingPost()
        .WithPath("/planets/10"))
    .AtPriority(1)
    .RespondWith(
        Response.Create()
        .WithStatusCode(HttpStatusCode.Created));

We set up two request mappings on the  WireMockServer instance. The first mapping is a POST request to any path starting with /planets/ with a priority of 5, and it responds with an HTTP status code of Unauthorized. The second mapping is a more specific POST request to the /planets/10 path with a higher priority of 1, and it responds with an HTTP status code of Created. The priority allows the server to prioritize matching the more specific mapping over the general one when multiple mappings match the incoming request.

Conclusion

In conclusion, WireMock.NET offers numerous benefits for effective integration testing. It enables the emulation of HTTP APIs, allowing the capture and direction of incoming requests to a server created by WireMock.NET. This capability empowers developers to define expectations, call the API, and verify its behavior.

WireMock.NET provides extensive features for HTTP response stubbing and testing, including matching stubs based on various criteria. WireMock.NET offers a robust and customizable solution for API mocking and testing.

It’s highly recommended to give WireMock.NET a try for integration testing needs, as it provides a comprehensive toolset for simulating API behavior and validating interactions.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!