Ответ
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), а не простые объекты-значения или статические утилиты.