How to Test API Integrations Using WireMock.Net in .NET 10
A beginner-friendly .NET 10 guide to testing API integrations with WireMock.Net: stub HTTP responses, simulate errors and delays, and write reliable tests.
The pretend shopkeeper
Imagine you are learning to run a small shop. Before you serve real customers, your teacher wants you to practise. So she stands behind a fake counter and pretends to be different customers.
"Give me one kilo of rice," she says. You weigh it and hand it over. "Now pretend I gave you a torn note," she says. You learn to check the note. "Now pretend the customer is angry and leaves," she says. You learn to stay calm.
The teacher is not a real customer. She is a stand-in. But because she behaves exactly like a real customer, you learn everything you need without risking a real sale.
WireMock.Net is that pretend shopkeeper for your code. When your .NET app needs to talk to some outside API, like a weather service or a payment gateway, you do not want your tests calling the real one. So you put WireMock.Net in the middle. It pretends to be that API. You tell it exactly how to behave. Your app never knows the difference.
This guide shows you how to use it, step by step, in plain words.
Why not just call the real API?
When your app calls a real outside API in a test, many things can go wrong. Let us look at them side by side.
| Problem with real API calls | What it means for your tests |
|---|---|
| The API can be slow | Your tests take minutes instead of seconds |
| The API can be down | Your tests fail even though your code is fine |
| The API costs money per call | Running tests many times gets expensive |
| The data keeps changing | You cannot predict the answer to check against |
| Errors are hard to trigger | You cannot easily test a 500 or a timeout |
A good test should be fast, repeatable, and fully under your control. Calling a real API breaks all three. WireMock.Net fixes all three.
What WireMock.Net actually is
WireMock.Net is a tiny web server that runs on your own machine, inside your test process. It is the .NET cousin of the original Java WireMock tool.
When it starts, it grabs a free port, like http://localhost:53219. You then teach it rules. Each rule says: "When a request comes in that looks like this, reply with that." We call each rule a stub.
Here is the key idea. Your app already uses an HttpClient to call the outside API. Normally that HttpClient points at the real API address. In a test, you simply point it at the WireMock address instead. Your app does not change. Only the address changes.
The WireMock testing loop
Steps
Start server
WireMockServer.Start() on a free port
Add stub
Teach it a request/response rule
Point client
HttpClient base URL = mock URL
Run code
Call the method under test
Check result
Assert the app handled it right
Installing WireMock.Net
WireMock.Net is free and open source. You add it to your test project, not your main app. Open a terminal in your test project folder and run one command.
dotnet add package WireMock.NetThat is all. There is no licence key and no sign-up. This matters because some popular .NET libraries (for example MediatR and MassTransit) have recently moved to paid commercial licences. WireMock.Net has not. It stays free for normal testing use.
A first, tiny example
Let us say your app has a small service that fetches a weather report. It uses an HttpClient and reads JSON. Here is a simplified version of the code we want to test.
public record Weather(string City, int TempC);
public class WeatherService(HttpClient httpClient)
{
public async Task<Weather?> GetWeatherAsync(string city)
{
// Calls GET /weather/{city} on whatever base address the client has.
var response = await httpClient.GetAsync($"/weather/{city}");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<Weather>();
}
}Notice the path uses GET /weather/{city}. The {city} part is a placeholder that gets filled in at runtime. The service does not care where the HttpClient points. That freedom is exactly what lets us swap in WireMock during a test.
Now here is the test. It uses xUnit, which is a common test framework in .NET.
using System.Net;
using WireMock.Server;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using Xunit;
public class WeatherServiceTests
{
[Fact]
public async Task GetWeather_ReturnsParsedWeather()
{
// 1. Start the fake server on a free port.
var server = WireMockServer.Start();
// 2. Teach it a rule: when GET /weather/pune comes in, reply with JSON.
server
.Given(Request.Create().WithPath("/weather/pune").UsingGet())
.RespondWith(Response.Create()
.WithStatusCode(HttpStatusCode.OK)
.WithHeader("Content-Type", "application/json")
.WithBodyAsJson(new { city = "Pune", tempC = 31 }));
// 3. Point an HttpClient at the fake server's URL.
var client = new HttpClient { BaseAddress = new Uri(server.Url!) };
var sut = new WeatherService(client);
// 4. Run the real code.
var result = await sut.GetWeatherAsync("pune");
// 5. Check it parsed correctly.
Assert.NotNull(result);
Assert.Equal("Pune", result!.City);
Assert.Equal(31, result.TempC);
server.Stop();
}
}Read the comments. They follow the exact five steps from the diagram above. The shape never really changes. Once you learn this shape, every WireMock test feels the same.
The fluent words explained
WireMock.Net uses a "fluent" style, where you chain method calls together like a sentence. Let us decode the most common words you will use. Keep this table handy.
| Method | What it does |
|---|---|
Request.Create() | Starts describing the request to match |
.UsingGet() / .UsingPost() | Match only this HTTP verb |
.WithPath("/weather/pune") | Match this URL path |
.WithParam("q", "rice") | Match a query string value like ?q=rice |
.WithHeader("X-Key", "abc") | Match a request header |
Response.Create() | Starts describing the reply |
.WithStatusCode(200) | Set the status code of the reply |
.WithBodyAsJson(obj) | Send a C# object back as JSON |
.WithDelay(...) | Wait before replying (to fake slowness) |
You read a stub from top to bottom like English: Given a request that uses GET with path /weather/pune, respond with status 200 and this JSON body. That readability is why so many teams like it.
Testing the sad paths
Happy tests are easy. Real value comes from testing what happens when things go wrong. This is where WireMock.Net shines, because you can make the fake API misbehave on purpose.
Simulating a server error
What should your app do when the API returns a 500? Maybe it should throw, maybe it should retry, maybe it should log. You decide, then you test it. Here you tell WireMock to fail.
[Fact]
public async Task GetWeather_WhenApiReturns500_Throws()
{
var server = WireMockServer.Start();
server
.Given(Request.Create().WithPath("/weather/delhi").UsingGet())
.RespondWith(Response.Create()
.WithStatusCode(HttpStatusCode.InternalServerError));
var client = new HttpClient { BaseAddress = new Uri(server.Url!) };
var sut = new WeatherService(client);
// EnsureSuccessStatusCode() should throw on a 500.
await Assert.ThrowsAsync<HttpRequestException>(
() => sut.GetWeatherAsync("delhi"));
server.Stop();
}Simulating a slow API
Networks are slow sometimes. If your app has a timeout, you want to prove the timeout actually works. WireMock can pause before replying using WithDelay.
server
.Given(Request.Create().WithPath("/weather/mumbai").UsingGet())
.RespondWith(Response.Create()
.WithStatusCode(HttpStatusCode.OK)
.WithBodyAsJson(new { city = "Mumbai", tempC = 33 })
.WithDelay(TimeSpan.FromSeconds(5)));If your HttpClient has a 2-second timeout, this stub lets you prove it gives up correctly instead of hanging forever.
Sad-path tests worth writing
Steps
500 error
API is broken
404 not found
Item missing
Slow / timeout
Network is slow
Bad JSON
API sends junk
Using WireMock with a full ASP.NET Core app
The examples so far tested one service in isolation. Often you want a bigger test: start your whole web app, send a real HTTP request to it, and have it call the outside API. The outside API is the part you fake with WireMock.
In ASP.NET Core, you start your app in memory using WebApplicationFactory. The trick is to override the base address that your app uses for the outside API, swapping in the WireMock URL.
Here is the shape of that bigger test. The important line is where we replace the API base address with the WireMock URL before the app starts.
public class WeatherApiTests : IClassFixture<WebApplicationFactory<Program>>
{
[Fact]
public async Task GetWeatherEndpoint_ReturnsOk()
{
var server = WireMockServer.Start();
server
.Given(Request.Create().WithPath("/weather/goa").UsingGet())
.RespondWith(Response.Create()
.WithStatusCode(HttpStatusCode.OK)
.WithBodyAsJson(new { city = "Goa", tempC = 29 }));
var factory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
// Override the outside API address with the mock URL.
builder.UseSetting("WeatherApi:BaseUrl", server.Url);
});
var client = factory.CreateClient();
// Call YOUR app's endpoint, not the mock directly.
var response = await client.GetAsync("/api/weather/goa");
response.EnsureSuccessStatusCode();
server.Stop();
}
}This test now covers the whole journey: routing, your controller or minimal API handler, your typed HttpClient, the JSON parsing, and the response back to the caller. Only the truly outside part, the third-party API, is faked.
Checking that your app sent the right request
So far we have controlled the reply. But sometimes you also want to confirm your app sent the correct request. Did it send the right header? Did it use POST? WireMock.Net records every request it receives, so you can inspect them afterwards.
var logs = server.LogEntries;
var sent = logs.Single().RequestMessage;
Assert.Equal("/weather/pune", sent.Path);
Assert.Equal("GET", sent.Method);This is powerful. It means WireMock is not only a stand-in for replies; it is also a witness that watches what your app does and lets you check it.
Keeping tests clean and fast
A few simple habits make your WireMock tests reliable.
- Start fresh each test. Either start a new
WireMockServerper test, or callserver.Reset()to clear old stubs. Leftover stubs from a previous test cause confusing failures. - Always stop the server. Call
server.Stop()at the end, or use a fixture that disposes it. Otherwise ports can pile up. - Let WireMock pick the port. Use
WireMockServer.Start()with no fixed port. A fixed port can clash when tests run side by side. - Keep stubs close to the test. Set up each stub right inside the test that needs it. This makes the test easy to read on its own.
When to use WireMock and when not to
WireMock is not the only mocking tool. Pick the right one for the job.
| You want to fake... | Best tool |
|---|---|
| An outside HTTP API | WireMock.Net |
| A C# interface in a unit test | Moq or NSubstitute |
| A real database | Testcontainers |
| Your whole app in memory | WebApplicationFactory |
Use WireMock when the thing you are faking talks over HTTP and you care about the real URL, headers, and JSON. If you only need to fake a plain C# interface, a normal mocking library is lighter and simpler.
A note on .NET 10 and modern C#
Everything here works on .NET 10, which is the current long-term support release, and with C# 14. The code samples use modern touches like primary constructors (the WeatherService(HttpClient httpClient) shorthand) and records. None of this is required by WireMock; it just keeps the examples short and clear. If you are on an older runtime, the same WireMock methods still work.
Quick recap
WireMock.Netis a small fake HTTP server you run inside your tests, like a pretend shopkeeper practising with you before real customers arrive.- It is free and open source. Install it into your test project with
dotnet add package WireMock.Net. - Every test follows the same five steps: start the server, add a stub, point your
HttpClientat the mock URL, run your code, then assert the result. - Use
Request.Create()to describe the incoming request andResponse.Create()to describe the reply, chained together in readable fluent style. - The biggest value is testing the sad paths:
500errors,404not found, slow responses withWithDelay, and bad data. - For full app tests, combine WireMock with
WebApplicationFactoryand override the outside API base URL with the mock URL. - WireMock also records requests, so you can confirm your app sent the right method, path, and headers.
- Keep each test fresh with
Reset()or a new server, always callStop(), and let WireMock pick a free port.
References and further reading
- WireMock.Net on GitHub (official repository)
- WireMock and .NET — official WireMock docs
- Integration tests in ASP.NET Core — Microsoft Learn
- Seamless Integration Testing With WireMock.NET — Code Maze
- Using WireMock in Integration Tests for ASP.NET Core APIs — Know Your Toolset
- How To Test Integrations with APIs Using WireMock in .NET — Anton DevTips
Related Posts
ASP.NET Core Integration Testing Best Practices (.NET 10)
A friendly .NET 10 guide to ASP.NET Core integration testing: WebApplicationFactory, real databases with Testcontainers, clean test isolation, and CI tips.
Creating Data-Driven Tests With xUnit (.NET 10)
A friendly .NET 10 guide to data-driven tests in xUnit: Theory, InlineData, MemberData, ClassData, strongly typed TheoryData, and xUnit v3 tips.
.NET Aspire Integration Testing: Best Practices for Distributed Apps
Learn .NET Aspire integration testing the simple way. Start your whole app, wait for services, and test how they really work together.
Testcontainers: Integration Testing Using Docker in .NET
A friendly .NET 10 guide to Testcontainers: spin up real databases in Docker for trustworthy integration tests, with xUnit, WebApplicationFactory, and clean cleanup.
Testcontainers Best Practices for .NET Integration Testing (.NET 10)
A friendly .NET 10 guide to Testcontainers: real databases in Docker, shared container fixtures, clean test isolation, faster CI, and the mistakes to avoid.
Simplify Assertions in Unit and Integration Tests With Verify in .NET
A friendly .NET 10 guide to Verify snapshot testing: cut long assertions, check big objects and JSON, and keep tests clean in xUnit, NUnit, and MSTest.