Featured image of post Why my WireMock mocks aren't working?

Why my WireMock mocks aren't working?

How to troubleshoot WireMock.Net problems + four common problems

Follow me

Introduction Link to this section

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? Link to this section

WireMock.Net is a library for stubbing and mocking HTTP APIs. I wrote about how and why to use it previously, and will use the previous post examples in this one.

WireMock.Net’s Admin API Link to this section

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.

Mappings endpoint Link to this section

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[
  {
    "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 Link to this section

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
[
  {
    "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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
[
  {
    "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, we see that the path was configured to /pokemon/charmander, instead of the /pokemon/squirtle that was in the request.

Configuring the Admin Interface Link to this section

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

var wiremockServer = WireMockServer.StartWithAdminInterface();

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

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

Full test code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[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 Link to this section

Query string params Link to this section

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

The mock below won’t work:

1
2
3
4
5
6
7
8
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:

1
2
3
4
5
6
7
8
9
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 Link to this section

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:

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
6
7
8
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 Link to this section

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

Shared WireMock server for all tests Link to this section

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
[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();
}
đŸ’Ŧ Like or have something to add? Leave a comment below.
Ko-fi
GitHub Sponsor
Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy