Why my WireMock mocks aren't working?
Tags: csharp, dotnet, testing, webdev
Published: 2023-04-12 09:10:31

## Introduction

WireMock.Net is a great tool to remove external dependencies when writing integration tests, but because it is highly configurable, it can be hard to find why its mocks aren’t working.

In this post, I’ll explain how to troubleshoot problems in its configuration and show some common problems that happen in my day-to-day work.

## What is WireMock.Net?

WireMock.Net is a library for stubbing and mocking HTTP APIs. I wrote about how and why to use it [previously](blog.genezini.com/p/integration-tests-without-api-dependencies-with-asp.net-core-and-wiremock.net/), and will use the previous post examples in this one.

## WireMock.Net's Admin API

WireMock.Net has an Admin API that is essential for debugging problems in our mocks.

The API offers many endpoints, but I'll show the two that will be used to find problems in the mocks.

> ℹ️ The complete list of endpoints can be seen in the [WireMock.Net's documentation](https://github.com/WireMock-Net/WireMock.Net/wiki/Admin-API-Reference#supported-interfaces).

### Mappings endpoint

The endpoint `/__admin/mappings` returns the mappings configured for the mocks, including all the matchers that need to be fulfilled for the mock to respond, and the response that will be returned.

Here is an example of the return:

```json
[
  {
    "Guid": "4dbbabf7-aac9-4f31-9fb9-a02360a454ea",
    "Request": {
      "Path": {
        "Matchers": [
          {
            "Name": "WildcardMatcher",
            "Pattern": "/pokemon/charmander",
            "IgnoreCase": false
          }
        ]
      },
      "Methods": [
        "GET"
      ]
    },
    "Response": {
      "StatusCode": 200,
      "BodyDestination": "SameAsSource",
      "Body": "{\"abilities\":[{\"ability\":{\"name\":\"name8f22045d-c183-4f1c-a32d-aaa6d337a9b7\",\"url\":\"url768c48e4-0dec-4952-b810-06472f602b4d\"},\"is_hidden\":true,\"slot\":192},
      ...
      \"url\":\"url716f6971-abd8-4e35-a9af-b7bd7a9a9cd6\"}}],\"weight\":128}",
      "Headers": {
        "Content-Type": "application/json"
      }
    },
    "UseWebhooksFireAndForget": false
  }
]
```

### Requests endpoint

The endpoint `/__admin/requests` returns the history of requests made to WireMock. It includes information about the request made and the response received by the requester.

It also includes the `PartialRequestMatchResult` property, that shows which matcher was successful and which was not.

Here is an example of a request that reached the mock:

```json
[
  {
    "Guid": "d2759bea-e4cc-442b-a63c-506ac6d61527",
    "Request": {
      "ClientIP": "::1",
      "DateTime": "2023-04-09T21:23:52.2879698Z",
      "Path": "/pokemon/charmander",
      "AbsolutePath": "/pokemon/charmander",
      "Url": "http://localhost:64378/pokemon/charmander",
      "AbsoluteUrl": "http://localhost:64378/pokemon/charmander",
      "Query": {},
      "Method": "GET",
      "Headers": {
        "Host": [
          "localhost:64378"
        ],
        "traceparent": [
          "00-fc2bc88c5bfc8b273e12d5a64cfd67cc-4ffca316becbeb2b-00"
        ]
      },
      "Cookies": {}
    },
    "Response": {
      "StatusCode": 200,
      "Headers": {
        "Content-Type": [
          "application/json"
        ]
      },
      "BodyDestination": "SameAsSource",
      "Body": "{\"abilities\":[{\"ability\":{\"name\":\"name8f22045d-c183-4f1c-a32d-aaa6d337a9b7\",\"url\":\"url768c48e4-0dec-4952-b810-06472f602b4d\"},\"is_hidden\":true,\"slot\":192},{\"ability\":{\"name\":\"name7e833a25-8050-4369-a5b9-811b4b00ee69\",
      ...
      \"url\":\"url716f6971-abd8-4e35-a9af-b7bd7a9a9cd6\"}}],\"weight\":128}",
      "BodyEncoding": {
        "CodePage": 65001,
        "EncodingName": "Unicode (UTF-8)",
        "WebName": "utf-8"
      },
      "DetectedBodyType": 1
    },
    "MappingGuid": "4dbbabf7-aac9-4f31-9fb9-a02360a454ea",
    "RequestMatchResult": {
      "TotalScore": 2.0,
      "TotalNumber": 2,
      "IsPerfectMatch": true,
      "AverageTotalScore": 1.0,
      "MatchDetails": [
        {
          "Name": "PathMatcher",
          "Score": 1.0
        },
        {
          "Name": "MethodMatcher",
          "Score": 1.0
        }
      ]
    },
    "PartialMappingGuid": "4dbbabf7-aac9-4f31-9fb9-a02360a454ea",
    "PartialRequestMatchResult": {
      "TotalScore": 2.0,
      "TotalNumber": 2,
      "IsPerfectMatch": true,
      "AverageTotalScore": 1.0,
      "MatchDetails": [
        {
          "Name": "PathMatcher",
          "Score": 1.0
        },
        {
          "Name": "MethodMatcher",
          "Score": 1.0
        }
      ]
    }
  }
]
```

The `RequestMatchResult` shows that the mock has 2 matchers configured (`TotalNumber`) and the total score was 2 (`TotalScore`). The `MatchDetails` also shows a score of 1.0 (100%) for all the matchers.

Here is an example of a request that didn't fulfill all of the matchers:

```json
[
  {
    "Guid": "60812be4-2d28-436b-afe3-4597b57e1995",
    "Request": {
      "ClientIP": "::1",
      "DateTime": "2023-04-10T22:09:24.4115811Z",
      "Path": "/pokemon/squirtle",
      "AbsolutePath": "/pokemon/squirtle",
      "Url": "http://localhost:57546/pokemon/squirtle",
      "AbsoluteUrl": "http://localhost:57546/pokemon/squirtle",
      "Query": {},
      "Method": "GET",
      "Headers": {
        "Host": [
          "localhost:57546"
        ],
        "traceparent": [
          "00-f89a0136b596e297767aea9602ed6df2-3e4ef33765b915d1-00"
        ]
      },
      "Cookies": {}
    },
    "Response": {
      "StatusCode": 404,
      "Headers": {
        "Content-Type": [
          "application/json"
        ]
      },
      "BodyAsJson": {
        "Status": "No matching mapping found"
      },
      "DetectedBodyType": 2
    },
    "PartialMappingGuid": "bab89116-6318-4ecb-8460-39e5116aeaec",
    "PartialRequestMatchResult": {
      "TotalScore": 1.0,
      "TotalNumber": 2,
      "IsPerfectMatch": false,
      "AverageTotalScore": 0.5,
      "MatchDetails": [
        {
          "Name": "PathMatcher",
          "Score": 0.0
        },
        {
          "Name": "MethodMatcher",
          "Score": 1.0
        }
      ]
    }
  }
]
```

The `PartialRequestMatchResult` shows that the mock has 2 matchers configured (`TotalNumber`) and the total score was 1 (`TotalScore`). In the `MatchDetails` we can see that the problem was on the `PathMatcher`.

Looking in the [mappings endpoint](#mappings-endpoint), we see that the path was configured to `/pokemon/charmander`, instead of the `/pokemon/squirtle` that was in the request.

## Configuring the Admin Interface

To use the admin interface, we just need to start WireMock's server with the `StartWithAdminInterface` method instead of the `Start` method:

```csharp {linenos=false}
var wiremockServer = WireMockServer.StartWithAdminInterface();
```

Then, to be able to access the admin interface, we include a delay after the **Act** part of the test:

```csharp {linenos=false}
await Task.Delay(TimeSpan.FromMinutes(100));
```

Lastly, we access the endpoint using the URL returned by the `Url` property of the `wireMockSvr` object:

![Debugging and accessing WireMock's admin endpoints](https://blog.genezini.com/p/why-my-wiremock-mocks-arent-working/images/debugging.gif)

Full test code:

```csharp
[Fact]
public async Task Get_Existing_Pokemon_Returns_200()
{
    //Arrange
    var wireMockSvr = WireMockServer.StartWithAdminInterface(); //Start WireMock with Admin Interface

    var Factory = _factory
        .WithWebHostBuilder(builder =>
        {
            builder.UseSetting("PokeApiBaseUrl", wireMockSvr.Url);
        });

    var HttpClient = Factory.CreateClient();

    Fixture fixture = new Fixture();

    var ResponseObj = fixture.Create<PokemonInfo>();
    var ResponseObjJson = JsonSerializer.Serialize(ResponseObj);

    wireMockSvr
        .Given(Request.Create()
            .WithPath("/pokemon/charmander")
            .UsingGet())
        .RespondWith(Response.Create()
            .WithBody(ResponseObjJson)
            .WithHeader("Content-Type", "application/json")
            .WithStatusCode(HttpStatusCode.OK));

    //Act
    var HttpResponse = await HttpClient.GetAsync("/pokemoninfo/charmander");

    await Task.Delay(TimeSpan.FromMinutes(30)); //Delay to be able to examine the admin interface

    //Assert
    HttpResponse.StatusCode.Should().Be(HttpStatusCode.OK);

    var ResponseJson = await HttpResponse.Content.ReadAsStringAsync();
    var PokemonInfo = JsonSerializer.Deserialize<PokemonInfo>(ResponseJson);

    PokemonInfo.Should().BeEquivalentTo(ResponseObj);

    wireMockSvr.Stop();
}
```

## Common problems

### Query string params

Let's take the endpoint `/pokemon?type={typeName}` as an example.

The mock below won't work:

```csharp
WireMockSvr
    .Given(Request.Create()
        .WithPath("/pokemon?type=fire")
        .UsingGet())
    .RespondWith(Response.Create()
        .WithBody(ResponseObjJson)
        .WithHeader("Content-Type", "application/json")
        .WithStatusCode(HttpStatusCode.OK));
```

To create a mock for endpoints with query strings, we have to use the `WithParam` property:

```csharp
WireMockSvr
    .Given(Request.Create()
        .WithPath("/pokemon")
        .WithParam("type", "fire")
        .UsingGet())
    .RespondWith(Response.Create()
        .WithBody(ResponseObjJson)
        .WithHeader("Content-Type", "application/json")
        .WithStatusCode(HttpStatusCode.OK));
```

### Params in the route

Contrary to params in the query string, params in the path won't work when configured with `WithParam`. For example, the endpoint `/pokemon/{pokemonName}` won't be reached with:

```csharp
WireMockSvr
    .Given(Request.Create()
        .WithPath("/pokemon")
        .WithParam("pokemonName", "charmander")
        .UsingGet())
    .RespondWith(Response.Create()
        .WithBody(ResponseObjJson)
        .WithHeader("Content-Type", "application/json")
        .WithStatusCode(HttpStatusCode.OK));
```

It has to be set in the `WithPath` method:

```csharp
WireMockSvr
    .Given(Request.Create()
        .WithPath("/pokemon/charmander")
        .UsingGet())
    .RespondWith(Response.Create()
        .WithBody(ResponseObjJson)
        .WithHeader("Content-Type", "application/json")
        .WithStatusCode(HttpStatusCode.OK));
```

### Running behind a network proxy

When running behind a network proxy, WireMock may be unreachable, causing a timeout in the application.

To ignore the proxy for `localhost`, we need to configure the `no_proxy` environment variable, adding `localhost` to its value (more values can be included, separated by comma):

![no_proxy environment variable configuration](https://blog.genezini.com/p/why-my-wiremock-mocks-arent-working/images/noproxy.png)

### Shared WireMock server for all tests

Sharing WireMock's server instance between tests can cause random problems because mock definitions are overridden when configured for the second time. For example, take two tests running in parallel:

- Test 1 configures `/pokemon/charmander` to return status `200`;
- Test 2 configures `/pokemon/charmander` to return status `404`.

The first test to configure the mock will break because its mock will be overridden and the result won't be as expected.

To avoid this random problems, we need to use one WireMock instance for each test:

```csharp
[Fact]
public async Task Get_Existing_Pokemon_Returns_200()
{
    var WireMockSvr = WireMockServer.StartWithAdminInterface();

    ...

    WireMockSvr.Stop();
}

[Fact]
public async Task Get_NotExisting_Pokemon_Returns_404()
{
    var WireMockSvr = WireMockServer.StartWithAdminInterface();

    ...

    WireMockSvr.Stop();
}
```

## References and Links

- [WireMock's Admin API Documentation](https://github.com/WireMock-Net/WireMock.Net/wiki/Admin-API-Reference#supported-interfaces)