Ответ
Moq (произносится "Mock-you") — это библиотека для .NET, предназначенная для создания мок-объектов (test doubles) в модульных тестах. Её основная цель — изолировать тестируемый класс, подменяя его реальные зависимости контролируемыми "заглушками" с предопределённым поведением.
Зачем это нужно? Представьте, вы тестируете OrderService, который зависит от IEmailService. Вместо того чтобы в тестах реально отправлять письма (это медленно и имеет побочные эффекты), вы создаёте мок IEmailService, который лишь имитирует отправку, позволяя проверить, что OrderService корректно его вызывает.
Ключевые концепции Moq:
Mock<T>: Основной класс для создания мока интерфейсаTили класса с виртуальными членами.Setup(): Настройка поведения мока. Вы указываете, какой метод/свойство вызывается и что должно произойти.Returns(): Определяет значение, которое должен вернуть настроенный метод.Verify(): Проверка (assert), что определённый метод был вызван с ожидаемыми параметрами нужное количество раз.
Практический пример:
// Интерфейс зависимости
public interface IOrderRepository
{
Order GetOrder(int id);
void Save(Order order);
}
// Класс, который мы тестируем
public class OrderProcessor
{
private readonly IOrderRepository _repository;
public OrderProcessor(IOrderRepository repository) => _repository = repository;
public bool ProcessOrder(int orderId)
{
var order = _repository.GetOrder(orderId);
if (order == null) return false;
order.Status = "Processed";
_repository.Save(order);
return true;
}
}
// ТЕСТ с использованием Moq и xUnit
[Fact]
public void ProcessOrder_ValidOrder_UpdatesStatusAndSaves()
{
// 1. ARRANGE: Подготовка данных и создание мока
var testOrder = new Order { Id = 123, Status = "New" };
var mockRepo = new Mock<IOrderRepository>();
// Настраиваем мок: при вызове GetOrder с аргументом 123 вернуть testOrder
mockRepo.Setup(repo => repo.GetOrder(123)).Returns(testOrder);
// Настройка для Save не требуется, если нам важен только факт вызова
var processor = new OrderProcessor(mockRepo.Object);
// 2. ACT: Выполнение тестируемого метода
bool result = processor.ProcessOrder(123);
// 3. ASSERT: Проверки
Assert.True(result); // Метод вернул true
Assert.Equal("Processed", testOrder.Status); // Статус изменился
// Верификация: убеждаемся, что Save был вызван ровно 1 раз с нашим order
mockRepo.Verify(repo => repo.Save(testOrder), Times.Once);
}
Важные особенности и лучшие практики:
- Мокирование классов: Moq может мокировать только виртуальные методы, свойства и абстрактные члены классов. Для моков предпочтительнее использовать интерфейсы.
- Строгие (Strict) vs Нестрогие (Loose) моки: По умолчанию Moq создаёт нестрогие моки. Вызов ненастроенного метода вернёт значение по умолчанию.
MockBehavior.Strictвыбросит исключение при любом неожиданном вызове, что делает тесты более хрупкими. - Проверка аргументов:
VerifyиSetupподдерживают гибкую проверку аргументов черезIt.IsAny<T>(),It.Is<T>(predicate).mockRepo.Verify(repo => repo.Save(It.Is<Order>(o => o.Status == "Processed")), Times.Once); - Не злоупотребляйте моками: Избыточное мокирование (over-mocking) усложняет тесты. Мокайте только внешние зависимости (база данных, файловая система, API), а не простые объекты-значения или статические утилиты.
Ответ 18+ 🔞
Слушай, вот есть такая штука — Moq (читается "мок-ю", типа "мокует тебя"). Это библиотека для .NET, чтобы в тестах создавать эти... как их... подставные объекты, ну, заглушки. Суть в чём: ты тестируешь один класс, а он там с кем-то общается — с базой данных, с почтовым сервисом, с какой-нибудь другой хренью. Так вот, вместо того чтобы в тестах реально лезть в базу (это медленно, нестабильно и вообще пиздец), ты подсовываешь ему подделку, которую сам и настраиваешь. И всё изолированно, красиво.
Ну зачем это, блядь, надо? Ну смотри. Допустим, у тебя есть OrderService, и он шлёт письма через IEmailService. В тесте тебе не нужно, чтобы письма реально улетали — тебе нужно просто проверить, что твой сервис пытается его вызвать с правильными параметрами. Вот для этого мок и нужен.
Основные штуки в Moq, которые надо знать:
Mock<T>: Это сам муляж. Создаёшь его для интерфейсаTили для класса (но там только виртуальные методы подцепятся, так что с интерфейсами проще).Setup(): Этим ты настраиваешь поведение. Говоришь: "вот когда вызовут такой-то метод с такими-то аргументами — сделай вот это".Returns(): А этим ты указываешь, что конкретно должен вернуть настроенный метод. Ну или выбросить исключение.Verify(): Это уже проверка (assert). После того как код отработал, ты можешь спросить у мока: "Слушай, а тебя вообще вызывали? А сколько раз? А с теми ли параметрами?"
Давай на живом примере, а то нихуя не понятно:
// Допустим, есть такой интерфейс репозитория, который ходит в базу
public interface IOrderRepository
{
Order GetOrder(int id);
void Save(Order order);
}
// А вот наш сервис, который мы хотим протестировать
public class OrderProcessor
{
private readonly IOrderRepository _repository;
public OrderProcessor(IOrderRepository repository) => _repository = repository;
public bool ProcessOrder(int orderId)
{
var order = _repository.GetOrder(orderId);
if (order == null) return false; // Если заказа нет — false
order.Status = "Processed"; // Меняем статус
_repository.Save(order); // Сохраняем
return true;
}
}
// А вот сам тест (допустим, xUnit)
[Fact]
public void ProcessOrder_ValidOrder_UpdatesStatusAndSaves()
{
// 1. ARRANGE: Готовим всё, что нужно
var testOrder = new Order { Id = 123, Status = "New" };
// Создаём муляж репозитория
var mockRepo = new Mock<IOrderRepository>();
// Настраиваем его: "Когда вызовут GetOrder с аргументом 123 — верни наш testOrder"
mockRepo.Setup(repo => repo.GetOrder(123)).Returns(testOrder);
// Метод Save пока не настраиваем — нам главное, что он вызовется
// Создаём наш тестируемый сервис, подсовывая ему муляж
var processor = new OrderProcessor(mockRepo.Object);
// 2. ACT: Запускаем тестируемый метод
bool result = processor.ProcessOrder(123);
// 3. ASSERT: Проверяем, что всё прошло как надо
Assert.True(result); // Метод вернул true
Assert.Equal("Processed", testOrder.Status); // Статус поменялся
// И самое главное — проверяем, что Save был вызван ровно ОДИН раз с нашим order
mockRepo.Verify(repo => repo.Save(testOrder), Times.Once);
}
Важные моменты, чтобы не наступить в говно:
- Классы vs Интерфейсы: Moq может мокать классы, но только их виртуальные методы, свойства и абстрактные члены. С интерфейсами — никаких проблем, поэтому их и любят.
- Strict vs Loose: По умолчанию моки "свободные" (Loose). Если вызвали метод, который не настраивали — он просто вернёт
default. А можно создать мок в режимеMockBehavior.Strict— тогда за любой неожиданный вызов он тебе исключение кинет. Но это обычно overkill, тесты становятся хрупкими, как яичная скорлупа. - Проверка аргументов: В
VerifyиSetupможно не конкретные значения указывать, а условия черезIt.IsAny<T>()илиIt.Is<T>(predicate).// Проверим, что сохранили любой заказ, но у которого статус "Processed" mockRepo.Verify(repo => repo.Save(It.Is<Order>(o => o.Status == "Processed")), Times.Once); - Не перемудри: Главная ошибка — начинать мокать всё подряд. Мокай только внешние зависимости (база, файлы, API, сервисы), а не простые DTO или хелперы. Иначе тест превратится в поддерживать ебаный ад, где ты больше времени будешь настраивать моки, чем проверять логику.