Wire Mocks (Mock API Using .Net)

“The core feature of WireMock is the ability to return predefined HTTP responses for requests matching criteria.”

I would create these Mocks (Stubs really) grouped by feature, ie: MyWorker\stubs\MyWorker.Stubs\Services\FooService which is the same logical manner we group class files in code.

Setup Project

The simplest way is to use BackgroundService running in a console application

  1. Use the VS GUI scaffold to create a new Console Application

  2. Create all the WireMockServer‘s using avalible ports, we normally call this file Stubs.cs, the example below will create wire mock servers on ports 6010 & 6020. These represent Restful APIs your application will interact with in development and/or under test. You would configure these in appsettings.Development.json / appsettings.Test.json.

1
2
3
4
5
6
7
8
9
using WireMock.Server;

namespace MyWorker.Stubs;

public static class Stubs
{
public static readonly WireMockServer MyService1 = WireMockServer.Start(6010);
public static readonly WireMockServer MyService2 = WireMockServer.Start(6020);
}
  1. Create the known contracts as DTOs and the static ids you will call with expecting x result

Example DTO

1
2
3
4
5
6
7
namespace MyWorker.Stubs.Services.MyService1;

public class FooDto
{
public Guid? Id { get; set; }
public string Description { get; set; }
}

Example known Ids

1
2
3
4
5
6
7
8
9
10
11
using System;

namespace MyWorker.Stubs.Common;

/// <summary>
/// <see href="https://somedocs">See Subs Documentation
/// </summary>
public static class KnownFoos
{
public static Guid MyKnownFooId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-111111111111");
}
  1. Create the mocked services
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
namespace MyWorker.Stubs.Services.MyService1;

public static class MyService1Stubs
{
public static void Init(WireMockServer server)
{
Console.WriteLine("Starting MyService1 Stubs...");

var atPriority = 1;
var responseBody = new FooDto()
{
Id = KnownFoos.MyKnownFooId,
Description = "Yeah baby!"
}
var request = Request
.Create()
.UsingGet()
.WithPath("/foos")
.WithParam("fooId", new ExactMatcher(KnownFoos.MyKnownFooId.ToString()));
server.SetupResponse(
responseBody,
request,
atPriority,
HttpStatusCode.OK);

// Default to bad request (higher priority of 100)
server.SetupResponse(
Request.Create().WithPath("/foos"),
100,
HttpStatusCode.BadRequest);
}
}

Its common to abstract logic away into extension methods, as the mock/stub grows it will be harder to maintain if you dont add these abstractions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace MyWorker.Stubs.Services.MyService1;

public static class MyService1Extensions
{
public static void SetupGetFoos(
this WireMockServer server,
Guid fooId,
object responseBody,
HttpStatusCode httpStatusCode,
int atPriority = 1)
{
var request = Request
.Create()
.UsingGet()
.WithPath("/foos")
.WithParam("fooId", new ExactMatcher(fooId.ToString()));

server.SetupResponse(
responseBody,
request,
atPriority,
httpStatusCode);
}
}

SetupResponse is futher abstraction which should only be added when sensible to allow re-use and mitigate repetition.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace MyWorker.Stubs.Common;

public static class WireMockExtensions
{
public static void SetupResponse(
this WireMockServer server,
object responseBody,
IRequestBuilder request,
int atPriority,
HttpStatusCode statusCode)
{
var response = Response
.Create()
.WithStatusCode(statusCode)
.WithJsonResponse(responseBody);

server.SetupServer(
request,
atPriority,
response);
}
  1. Create the StubsHub, we normally call this file StubsHub.cs, then inherit and implement BackgroundService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;

namespace MyWorker.Stubs;

public class StubsHub : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
Console.WriteLine("Starting Stubs...");

MyService1Stubs.InitialiseStubs(Stubs.MyService1);
MyService2Stubs.InitialiseStubs(Stubs.MyService2);

return Task.CompletedTask;
}
}

Other methods

Beside the simple var request = Request.Create().UsingGet() ... there are other methods in the fluent API.

ExactMatcher

1
2
3
4
5
var request = Request
.Create()
.UsingPost()
.WithHeader(HeaderConstants.ClientName, new ExactMatcher(TestConstants.SomeKnownClientName))
.WithPath("/foos");

RegexMatcher

1
2
3
4
5
6
var request = Request
.Create()
.UsingPost()
.WithPath("/foo.svc")
.WithHeader("SOAPAction", new RegexMatcher(".*/somepath/someaction"))
.WithBody(new XPathMatcher($"//*[local-name() = 'fooID'][text()='{KnownFoos.MyKnownFooId.ToString()}']"))

JsonPartialMatcher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var ignoreCase = true;
var options = new JsonSerializerOptions()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Converters = { new JsonStringEnumConverter() }
};
var matchOn = JsonSerializer.Serialize(partialMatchObject, options);

var request = Request
.Create()
.UsingPost()
.WithPath("/foos")
.WithBody(new JsonPartialMatcher(matchOn, ignoreCase));

Docker

Do you even code if you dont package up your things into a nice container :D

  1. Create the Stubs.Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env

WORKDIR /code
COPY ["MyWorker.Stubs/MyWorker.Stubs.csproj", "MyWorker.Stubs/"]

# --- Restore project (build)
RUN dotnet restore "MyWorker.Stubs/MyWorker.Stubs.csproj"

# --- Publish
COPY MyWorker.Stubs/ MyWorker.Stubs/
WORKDIR /code/MyWorker.Stubs
RUN dotnet publish "MyWorker.Stubs.csproj" -c Release -o /out

FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app

# --- Copy the compiled code from build-env and set entrypoint
COPY --from=build-env /out .
ENTRYPOINT ["dotnet", "MyWorker.Stubs.dll"]
  1. Create the docker compose file docker-compose.yml
1
2
3
4
5
6
7
8
9
10
version: "3.8"
services:
stubs_services:
container_name: fooapi_stubs_services
build:
context: .
dockerfile: Stubs.Dockerfile
ports:
- 6010:6010 # MyService1
- 6020:6020 # MyService2
  1. Now run the mock/stubs
1
docker-compose --project-name foo_worker -f ./stubs/docker-compose.yml up --build

The mocked services are then avalible at the routes below, they will respond to matched requests.

References