В чем проблема тестируемости контроллера с большой логикой и работой с базой?
Что проверяют
Понимаете ли вы, почему «толстый» контроллер плохо тестируется, и умеете ли разложить его на слои в ASP.NET Core. Следом обычно спрашивают, как вы будете тестировать работу с базой: мокать DbContext или поднимать настоящую базу.
Как отвечать
Проблемы толстого контроллера:
- смешение ответственности: разбор запроса, валидация, бизнес-правила и SQL в одном методе. Чтобы проверить одно правило, приходится проходить весь путь запроса;
- жёсткие зависимости:
new SqlConnection(), статические хелперы,DateTime.Nowпрямо в коде. Их не подменить в тесте, результат зависит от окружения и времени; - состояние базы: тесту нужна настроенная база с данными, он медленный, падает из-за чужих записей и плохо идёт параллельно;
- комбинаторика: каждый новый
ifудваивает число сценариев, а проверить их можно только через HTTP.
Как исправить:
- тонкий контроллер: принимает запрос, вызывает сервис и превращает результат в
IActionResultилиActionResult<T>; - логика в сервисе: зависимости через интерфейсы и конструктор, регистрация в DI. Время через
TimeProvider, который появился в .NET 8, а неDateTime.Now; - доступ к данным:
DbContextсам по себе реализует unit of work и репозиторий. Отдельный репозиторий оправдан, когда нужно спрятать сложные запросы.
Тесты при этом делятся на два уровня. Юнит-тесты на сервисы с моками через Moq или NSubstitute на xUnit. Интеграционные через WebApplicationFactory и настоящую базу в контейнере (Testcontainers): они проверяют маршрутизацию, сериализацию и реальные SQL-запросы.
Где спотыкаются
Мокают DbContext и DbSet вручную. Такие тесты проверяют моки, а не LINQ-запросы, которые на реальной базе могут вообще не транслироваться в SQL.
Берут EF Core InMemory как замену базе. Microsoft не рекомендует его для тестов: он не проверяет ограничения, транзакции и поведение настоящего провайдера. Надёжнее Testcontainers с той же СУБД, что в проде, SQLite в памяти это компромисс.
Выносят логику в сервис, но тянут туда HttpContext и конкретные классы. Тестируемость от переезда кода в другой файл сама не появляется.