Интеграционные тесты ASP.NET Core
Интеграционные тесты ASP.NET Core
Где применяют интеграционные тесты
Юнит-тест проверяет один класс: вы вызываете метод напрямую, зависимости подменяете моками. Интеграционный тест поднимает почти настоящее приложение — с Program.cs, middleware, маршрутами, JSON-сериализацией — и шлёт запросы как внешний клиент через HttpClient.
Так ловят ошибки, которые юнит не увидит:
- забыли
app.UseAuthentication(); - опечатка в
[Route]; - неверный порядок middleware;
- POST без
Content-Type: application/json.
В ASP.NET Core для этого есть WebApplicationFactory<Program>: тестовый хост в памяти, без ручного выбора порта в браузере.
Опираемся на API из 4511. С JWT — 4515. Solution в стиле Clean Architecture — 2143, MediatR — 4518. Контекст отладки: /encyclopedia/4-code-dev/4-14-razrabotka-i-otladka/intro.
Словарь
| Термин | Простыми словами |
|---|---|
| xUnit | Популярный фреймворк тестов в .NET ([Fact], dotnet test). |
| WebApplicationFactory | Создаёт тестовый экземпляр вашего веб-приложения. |
| Test server | HTTP обрабатывается в памяти, без реального сетевого сокета. |
| Fixture | Общая настройка на класс тестов (IClassFixture<...>). |
| InMemory DB | EF Core «база» в RAM для быстрых тестов (с ограничениями). |
Что получится
| Тест | Проверка |
|---|---|
Health_ReturnsOk | GET /health → 200 |
PostNote_ThenGetAll | POST + GET, тело JSON |
| (опционально) | Защищённый endpoint с Bearer после login |
Подготовка решения
Если API ещё нет — создайте по 4511:
dotnet new webapi -n HelloApi -o HelloApi --use-controllers
cd HelloApi
Добавьте endpoint (если нет):
app.MapGet("/health", () => Results.Ok(new { status = "ok" }));
public partial class Program
В .NET 6+ Program.cs часто без явного класса. Тестам нужен тип Program:
app.Run();
public partial class Program { }
В конце Program.cs — стандартный приём для WebApplicationFactory<Program>.
Проект тестов
Из корня решения:
dotnet new xunit -n HelloApi.Tests -o HelloApi.Tests
dotnet add HelloApi.Tests reference HelloApi
dotnet add HelloApi.Tests package Microsoft.AspNetCore.Mvc.Testing
dotnet sln add HelloApi.Tests/HelloApi.Tests.csproj
Microsoft.AspNetCore.Mvc.Testing подтягивает test host и фабрику приложения.
Базовый тест
HelloApi.Tests/HealthEndpointTests.cs:
using System.Net;
using Microsoft.AspNetCore.Mvc.Testing;
namespace HelloApi.Tests;
public class HealthEndpointTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public HealthEndpointTests(WebApplicationFactory<Program> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task Health_ReturnsOk()
{
var response = await _client.GetAsync("/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var json = await response.Content.ReadAsStringAsync();
Assert.Contains("ok", json, StringComparison.OrdinalIgnoreCase);
}
}
dotnet test
Что происходит внутри
| Шаг | Смысл |
|---|---|
IClassFixture<WebApplicationFactory<Program>> | Одна фабрика на класс — приложение поднимается один раз. |
factory.CreateClient() | HttpClient с BaseAddress на test server. |
GetAsync("/health") | Путь от корня; ведущий / важен. |
Assert.Equal | Проверка статуса и тела. |
Тест CRUD
При наличии NotesController из 4511:
using System.Net.Http.Json;
using Microsoft.AspNetCore.Mvc.Testing;
namespace HelloApi.Tests;
public class NotesEndpointTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public NotesEndpointTests(WebApplicationFactory<Program> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task PostNote_ThenGetAll_ContainsNote()
{
var create = await _client.PostAsJsonAsync(
"/api/notes",
new { Text = "integration test" });
create.EnsureSuccessStatusCode();
var notes = await _client.GetFromJsonAsync<List<NoteDto>>("/api/notes");
Assert.NotNull(notes);
Assert.Contains(notes, n => n.Text == "integration test");
}
private record NoteDto(int Id, string Text);
}
PostAsJsonAsync сериализует объект в JSON и ставит заголовок Content-Type: application/json — как настоящий клиент.
Подмена базы данных
Интеграционные тесты не должны писать в продакшен-PostgreSQL.
| Вариант | Когда |
|---|---|
| EF InMemory | Быстро; нет настоящего SQL |
SQLite :memory: | Ближе к SQL |
| Testcontainers | Реальный PostgreSQL в Docker на CI |
HelloApi.Tests/CustomWebApplicationFactory.cs:
using HelloApi.Data;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace HelloApi.Tests;
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));
if (descriptor is not null)
services.Remove(descriptor);
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("TestsDb"));
});
builder.UseEnvironment("Testing");
}
}
Тест:
public class NotesDbTests : IClassFixture<CustomWebApplicationFactory>
{
private readonly HttpClient _client;
public NotesDbTests(CustomWebApplicationFactory factory)
{
_client = factory.CreateClient();
}
// те же HTTP-вызовы
}
:::tip InMemory и транзакции
UseInMemoryDatabase не эмулирует все ограничения SQL Server. Для raw SQL и миграций — SQLite или Testcontainers.
:::
В Program.cs для внешних сервисов:
if (!app.Environment.IsEnvironment("Testing"))
{
// подключение к реальной почте, платежам и т.д.
}
Тест API с JWT
Для 4515:
var login = await _client.PostAsJsonAsync("/login",
new { email = "test@local", password = "Passw0rd!" });
login.EnsureSuccessStatusCode();
var auth = await login.Content.ReadFromJsonAsync<AuthDto>();
_client.DefaultRequestHeaders.Authorization =
new("Bearer", auth!.Token);
var secured = await _client.GetAsync("/api/notes");
secured.EnsureSuccessStatusCode();
private record AuthDto(string Token, DateTime ExpiresAt);
Альтернатива для большого сьюта: в ConfigureTestServices подставить тестового ClaimsPrincipal без реального JWT — быстрее, но не проверяет подпись токена.
Сравнение с юнит-тестом контроллера
WebApplicationFactory | Мок сервиса + вызов action | |
|---|---|---|
| Проверяет | Pipeline, routing, JSON, auth | Один метод |
| Скорость | Медленнее | Быстрее |
Видит Program.cs | Да | Нет |
Оба подхода дополняют друг друга.
Частые ошибки
| Симптом | Решение |
|---|---|
Program не найден | public partial class Program { } |
| 404 в тесте | Путь /health, не health |
| Пустая БД после POST | Подмена DbContext не сработала — проверьте тип в Remove |
| 307 redirect | HTTPS redirect: AllowAutoRedirect = false или тестируйте HTTPS |
| Падает только на CI | Общая InMemory БД — уникальное имя на фабрику |
Что попробовать
- Ожидайте
400на POST с пустым телом (4517 —ValidationProblem). - Testcontainers + PostgreSQL на GitHub Actions.
- Отдельный сьют только для
AuthControllerс тестовымJwt:Keyвappsettings.Testing.json.
Дальше
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). C# как язык платформы .NET - устройство проекта, роль `.cs`-файлов и базовые принципы организации кода. C# — это современный, типизированный язык программирования общего назначения, разработанный корпорацией Microsoft. Справочник-шпаргалка по конфигурациям в C — типы, синтаксис, стандартная библиотека, типовые паттерны. Не заменяет пошаговое обучение. Учебный курс — раздел. Набор советов, правил, принципов и обычаев в разработке на этом языке. Кавычки, точки, запятые, скобки и прочие знаки препинания. Ключевые слова C# - назначение базовых конструкций языка и примеры их применения в типичном коде. Набор функций, которые включены в стандартную библиотеку языка. Пространства имён в C# - организация модулей, `file-scoped namespace` и поддержание чистой структуры кода. манипулировать данными (арифметические, логические, сравнительные операторы). Самый базовый способ ветвления — оператор if. Он проверяет условие и, если оно истинно (true), выполняет блок кода. Обработка исключений в C# - типы исключений, `try/catch/finally` и практики надежного кода. Платформо-зависимые исключения — например, PlatformNotSupportedException используется в кроссплатформенных API, когда функция недоступна на текущей ОС.C# - язык программирования платформы .NET
Что требуется знать перед началом изучения языка программирования C#
Справочник по конфигурациям в C#
Рекомендации по разработке на C#
Синтаксис и пунктуация в C#
Ключевые слова языка C#
Встроенные функции и методы C#
Пространства имён в C#
Управляющие конструкции и логические операторы
Условные выражения и ветвления
Обработка исключений в C#
Иерархия классов исключений в C#