Skip to main content
SEMastery
Testingbeginner

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.

12 min readUpdated January 13, 2026

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 callsWhat it means for your tests
The API can be slowYour tests take minutes instead of seconds
The API can be downYour tests fail even though your code is fine
The API costs money per callRunning tests many times gets expensive
The data keeps changingYou cannot predict the answer to check against
Errors are hard to triggerYou 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.

Without WireMock your test depends on the real outside API. With WireMock, the test controls a local fake.

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

Start server
Add stub
Point client
Run code
Check result

Steps

1

Start server

WireMockServer.Start() on a free port

2

Add stub

Teach it a request/response rule

3

Point client

HttpClient base URL = mock URL

4

Run code

Call the method under test

5

Check result

Assert the app handled it right

The five steps you repeat for every WireMock test.

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.Net

That 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.

MethodWhat 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.

How a single request flows through WireMock inside one test.

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

500 error
404 not found
Slow / timeout
Bad JSON

Steps

1

500 error

API is broken

2

404 not found

Item missing

3

Slow / timeout

Network is slow

4

Bad JSON

API sends junk

The four failure cases that catch the most real bugs.

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.

In a full integration test, the test calls your app, and your app calls WireMock.

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 WireMockServer per test, or call server.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.
The clean lifecycle of a WireMock server inside a test class.

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 APIWireMock.Net
A C# interface in a unit testMoq or NSubstitute
A real databaseTestcontainers
Your whole app in memoryWebApplicationFactory

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.Net is 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 HttpClient at the mock URL, run your code, then assert the result.
  • Use Request.Create() to describe the incoming request and Response.Create() to describe the reply, chained together in readable fluent style.
  • The biggest value is testing the sad paths: 500 errors, 404 not found, slow responses with WithDelay, and bad data.
  • For full app tests, combine WireMock with WebApplicationFactory and 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 call Stop(), and let WireMock pick a free port.

References and further reading

Related Posts