Инфраструктура ASP.NET в обработчиках MediatR

Я предпочитаю, чтобы мои обработчики освобождались от инфраструктуры ASP.NET, которую очень сложно проверить (да, даже в ASP.NET Core). Но иногда это происходит, и у вас есть зависимость, такая как UserManager (я хотел бы знать, что однажды, почему это не интерфейс), HttpContext и т.д., И модульные тесты превращаются в насмешливый ад.

Я попытался использовать тестирование интеграции, чтобы справиться с этим, создав TestServer и получив всю инфраструктуру ASP.NET для каждого вызова api. Он работает довольно хорошо, но иногда кажется излишним, если я хочу проверить простую логику моего обработчика. И хотя он решает техническую проблему издевательской инфраструктуры ASP.NET, он сохраняет архитектурную проблему (если вы так считаете) о наличии инфраструктуры ASP.NET в своих обработчиках.

Я хотел бы знать, какие рекомендуемые подходы к его решению?

Ответ 1

Я чувствую твою боль. Я наткнулся на фантастический пост от Джимми Богарда, который решает эту проблему, используя то, что Мартин Фаулер называет " Подкожные тесты". Я оставлю глубокое объяснение этим экспертам, но в двух словах подкожные тесты просто избегают всех трудных для тестирования аспектов пользовательского интерфейса.

Бесстыдный плагин: я сейчас нахожусь в процессе написания вики, демонстрирующей эти паттерны в образце сквозного проекта на github. За ним не трудно следовать, но, вероятно, слишком много кода для публикации SO ответа.

Подвести итоги:

  • Если вы используете MediatR правильно, ваши контроллеры должны быть очень тонкими, что делает их тестирование бессмысленным.
  • То, что вы хотите проверить, это ваши обработчики.
  • Однако вы хотите проверить свои обработчики как часть вашего реального конвейера.

Решать:

  1. Оберните запрос http в транзакции.
  2. Создайте тестовое устройство, имитирующее приложения Startup.cs
  3. Настроить тестовый сервер БД для выполнения запросов и команд, а также сбрасывать после каждого теста.

Это в основном это. Каждый раз, когда вы запускаете интеграционный тест для одного из ваших обработчиков:

  • Хостинговая среда является поддельной, но ваше приложение запускается в реальном тестировании.
  • Ваш запрос или команда заключены в транзакцию, имитирующую ваш DbContext.
  • Обработчик выполняется для реальной базы данных и затем сбрасывается.

Я бы добавил больше примеров кода в мой ответ, но между постом в блоге и предоставленной мною вики гораздо проще следовать там примерам кода.

Ответ 2

Я бы сказал, это зависит от уровня уверенности, который вы хотите получить в конце. Если вы хотите, чтобы вся система работала так, как ожидалось, возможно, интеграционные тесты с использованием TestServer - это путь.

Одно из преимуществ MediatR заключается в том, что он позволяет отделить вашу бизнес-логику от приложения, использующего его, поэтому на самом верхнем уровне, допустим, в контроллерах нет логики, а просто делегирования посреднику.

Сказав это, вы правы, что иногда вашей логике нужна информация от хостинга. Примером может служить пользователь, делающий запрос, доступный в контексте HTTP.

В этом случае, если вы хотите избежать установки тестового HTTP-сервера для проверки работы вашей логики, вы можете представить эту информацию в абстракции, и тогда ваш обработчик будет зависеть от этой абстракции. Тогда ваши тесты могли бы издеваться над этой зависимостью, используя реальную систему для всего остального.

Это имеет смысл?

Ответ 3

Mediatr или нет, вы всегда должны стараться, чтобы только основные правила проходили по логике в ваших контроллерах и вызывали из них классы бизнес-логики, чтобы выполнять фактическую работу. Когда вы вводите им интерфейсы в эту бизнес-логику, зависимости ваших контроллеров легко высмеиваются в ваших модульных тестах, и ваши тесты могут сфокусироваться, если они правильно реализуют эти интерфейсы и выполняют только основную работу по вводу/выводу маршрутизации. И ваша фактическая бизнес-логика может быть протестирована еще проще.

Для тех статических классов, которые, например, для чтения настроек web.config, одна из стратегий, которые мне нравятся, создает вокруг них интерфейс оболочки. В то время как ConfigurationManager статичен, я все еще могу просто написать обычный класс с интерфейсом, в который я помещал методы или свойства для чтения определенного параметра (предпочтительно семантически названного) из Configuration Manager. Теперь я могу легко высмеять любой сконфигурированный параметр (или его отсутствие) в своем тесте, просто издеваясь над интерфейсом и настраивая разные возвращаемые значения.