The ASP.NET Core Integration tests are great, but one of the harder parts is mocking of downstream services. There are a lot of discussions around this topic, for instance on StackExchange. This post introduces WireMock.Net – a useful tool to make mocking easier.
How do you mock responses for HTTP requests? Until now I have been implementing a custom HttpMessageHandler and injected this into the service under test. This method works, but has some downsides.
- First and foremost: Mocking is impossible for services where the HTTP client is newed up rather than injected through the DI. Never do this!
- Mocking edge cases like long response times etc. becomes harder and it is possible to make mistakes.
- As the service grows, the logic around matching requests and returning correct responses can become very complex. This is worse for services which call multiple downstream services.
When I searched yet again for libraries to make request matching and mocking easier, I found the solution to most of these problems: WireMock.Net! A mock HTTP service which offers request matchers, response builders, standard error scenarios and more!
How does WireMock.Net Work?
Using WireMock to mock downstream services in integration tests (like ASP.NET Core Integration Tests) is almost too easy. The same method works for unit tests as well.
Step 1:
Add the WireMock.Net package to the test project. Then Start a WireMock.Net mock service without selecting a port. The service will start listening for requests on a free port.
var server = WireMockServer.Start();
Note: To mock two different downstream dependencies create two WireMock.Net instances and start them. Then apply the following steps for each instance.
Step 2:
Get the port on which the mock server is listening. The URL for the mock server will be something like ‘http://localhost:3333’.
var port = server.Ports[0];
Step 3:
Configure responses for the expected requests.
server.Given( Request.Create().WithPath("/ping").UsingGet()) .RespondWith( Response.Create() .WithStatusCode(200) .WithBody("pong"));
Step 4:
Configure the SUT to consume the WireMock service. In a real life API this will look different, depending on how the downstream services are configured.
httpClient.BaseAddress = new Uri($"http://localhost:{port}");
Step 5:
Test the service. Below is a very basic test which shows the principle. For a real test I would use a generated client. Check my post about best practices for Integration Tests for more details.
var response = await client.GetAsync("/ping"); response.StatusCode.Should().Be(200); var body = await response.Content.ReadAsStringAsync(); body.Should().BeEquivalentTo("pong");
It is also possible to check the log to see which requests were made.
Step 6:
Consider performance and test structure. Starting up a WireMock server has an overhead!
This commit shows a sample where the mock server is shared between multiple tests. The complete sample code is available in GitHub: https://github.com/AngelaE/blog-integration-test
WireMock.Net as Standalone Service
WireMock.Net can also be run as a standalone service. In this case the port needs to be configured to be able to use the service later. In this mode WireMock.Net can be used similar to MounteBank or the original WireMock.
I have not tried this, but there could be an advantage to using WireMock.Net if the tests are written in C#.
Still Problems with Mocking?
If it is still too hard to mock downstream services, consider the following options:
- Splitting tests for cross-functional behavior from functional tests simplifies both the tests and required mocks. Samples:
- Cover authentication logic with a set of tests. Replace the authentication logic in functional tests with a test mock and concentrate on the domain logic there.
- Test cross-functional logic like correlation IDs, authorization, … separately. Then test only the specific functionality.
- Difficulties to mock dependencies like downstream services can indicate problems with the implementation of the distributed system. One sample for an anti-pattern could be to have a lot of downstream requests to the same service for one service call. Does the data being requested make sense? Do we have a lot of duplication? API gateways may do this to aggregate requests for specific clients. As long as the logic it is not too complex and the performance penalty is acceptable this is no problem.
- The data required may just be inherently difficult to mock.
Conclusion
Mocking downstream services is never easy – even with the best of tools. WireMock.NET can reduce a lot of complexity in the mock setup. Since it also is a REAL HTTP server, it makes integration tests a lot more realistic than implementing a custom message handler.
Disclaimer: Unfortunately I am working with old monoliths at the moment and cannot play with WireMock.Net in anger.