🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Testy integracyjne w ASP.NET Core — WebApplicationFactory krok po kroku

Test jednostkowy sprawdza jedną klasę w izolacji. Test integracyjny sprawdza, czy cały pipeline — routing, middleware, dependency injection, serializacja JSON — faktycznie działa razem, wysyłając prawdziwe żądanie HTTP do aplikacji uruchomionej w pamięci.

WebApplicationFactory — uruchomienie aplikacji w pamięci

using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;

public class ZamowieniaApiTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public ZamowieniaApiTests(WebApplicationFactory<Program> fabryka)
    {
        _client = fabryka.CreateClient();
    }

    [Fact]
    public async Task GetZamowienie_IstniejaceId_Zwraca200()
    {
        var odpowiedz = await _client.GetAsync("/api/zamowienia/1");

        odpowiedz.EnsureSuccessStatusCode();
    }
}

WebApplicationFactory<Program> uruchamia całą aplikację (łącznie z rzeczywistym Program.cs, middleware i konfiguracją DI) w pamięci procesu testowego — _client to prawdziwy HttpClient, który wysyła żądania do tej instancji, bez otwierania portu sieciowego.

Odczyt i deserializacja odpowiedzi

[Fact]
public async Task GetZamowienie_IstniejaceId_ZwracaPoprawneDane()
{
    var odpowiedz = await _client.GetAsync("/api/zamowienia/1");
    odpowiedz.EnsureSuccessStatusCode();

    var zamowienie = await odpowiedz.Content.ReadFromJsonAsync<ZamowienieDto>();

    Assert.NotNull(zamowienie);
    Assert.Equal(1, zamowienie.Id);
}

ReadFromJsonAsync<T> deserializuje odpowiedź tak samo, jak zrobiłby to prawdziwy klient — test przechodzi przez ten sam serializer JSON, który działa w produkcji.

Podmiana bazy danych na testową

Domyślnie WebApplicationFactory uruchamia aplikację z tą samą konfiguracją co produkcja — łącznie z prawdziwym connection stringiem do bazy danych. W testach chcesz to podmienić na bazę w pamięci:

public class TestowaFabryka : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var opisDeskryptora = services.SingleOrDefault(
                d => d.ServiceType == typeof(DbContextOptions<ZamowieniaDbContext>));
            if (opisDeskryptora != null) services.Remove(opisDeskryptora);

            services.AddDbContext<ZamowieniaDbContext>(options =>
                options.UseInMemoryDatabase("TestowaBaza"));
        });
    }
}

public class ZamowieniaApiTests : IClassFixture<TestowaFabryka>
{
    private readonly HttpClient _client;
    public ZamowieniaApiTests(TestowaFabryka fabryka) => _client = fabryka.CreateClient();
}

ConfigureWebHost pozwala nadpisać rejestracje z kontenera DI już po tym, jak Program.cs je skonfigurował — usuwasz prawdziwy DbContext i rejestrujesz w jego miejsce wersję z bazą w pamięci, zanim CreateClient() uruchomi aplikację.

Test POST — zapis danych i weryfikacja

[Fact]
public async Task PostZamowienie_PoprawneDane_Zwraca201IZapisujeWBazie()
{
    var noweZamowienie = new { KlientId = 5, Suma = 199.99m };

    var odpowiedz = await _client.PostAsJsonAsync("/api/zamowienia", noweZamowienie);

    Assert.Equal(HttpStatusCode.Created, odpowiedz.StatusCode);

    var lokalizacja = odpowiedz.Headers.Location;
    var sprawdzenie = await _client.GetAsync(lokalizacja);
    sprawdzenie.EnsureSuccessStatusCode();
}

Ten test sprawdza cały cykl: żądanie POST trafia przez routing do kontrolera, kontroler zapisuje przez DbContext do bazy w pamięci, zwraca 201 Created z nagłówkiem Location — a kolejne żądanie GET pod tym adresem faktycznie znajduje zapisany rekord. To coś, czego pojedynczy test jednostkowy kontrolera nigdy nie sprawdzi, bo mockowałby DbContext zamiast używać prawdziwego zapisu i odczytu.

Testy integracyjne vs jednostkowe — kiedy który

Test jednostkowy z Moq sprawdza logikę jednej klasy w izolacji — szybki, precyzyjny, ale nie wykryje błędu w konfiguracji routingu, złej rejestracji w DI, czy niekompatybilnego mapowania JSON. Test integracyjny z WebApplicationFactory sprawdza, czy te elementy faktycznie współpracują — wolniejszy (uruchamia całą aplikację), ale wyłapuje błędy integracji, których żaden test jednostkowy nie zobaczy. W praktyce potrzebujesz obu: dużo testów jednostkowych dla logiki biznesowej, kilku(nastu) testów integracyjnych dla najważniejszych ścieżek API.

Podsumowanie

WebApplicationFactory<Program> uruchamia całą aplikację ASP.NET Core w pamięci procesu testowego, dając prawdziwy HttpClient do wysyłania żądań przez pełny pipeline (routing, middleware, DI, serializacja). ConfigureWebHost pozwala podmienić zależności — najczęściej bazę danych na wersję w pamięci — zanim aplikacja wystartuje. To testy wolniejsze i bardziej kosztowne w utrzymaniu niż jednostkowe, ale sprawdzające coś, czego mock nigdy nie wykryje: czy poszczególne elementy aplikacji faktycznie działają razem.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →