Как протестировать интерфейс Consumer с помощью Unit Testing?

«Как протестировать интерфейс Consumer с помощью Unit Testing?» — вопрос из категории Тестирование, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Для тестирования зависимостей, реализующих интерфейс IConsumer<T>, используйте библиотеки для мокинга (Moq, NSubstitute) и проверяйте взаимодействие с ними в соответствии с контрактом.

Пример теста с Moq:

// Тестируемый сервис, зависящий от IConsumer<Order>
public class OrderProcessor
{
    private readonly IConsumer<Order> _consumer;

    public OrderProcessor(IConsumer<Order> consumer) 
        => _consumer = consumer;

    public void Process(Order order) 
        => _consumer.Consume(order);
}

// Тест
[Test]
public void Process_ShouldCallConsumerExactlyOnce_WithCorrectOrder()
{
    // Arrange
    var mockConsumer = new Mock<IConsumer<Order>>();
    var processor = new OrderProcessor(mockConsumer.Object);
    var testOrder = new Order { Id = 123 };

    // Act
    processor.Process(testOrder);

    // Assert
    mockConsumer.Verify(
        c => c.Consume(It.Is<Order>(o => o.Id == testOrder.Id)), 
        Times.Once
    );
}

Ключевые аспекты тестирования Consumer:

  1. Изоляция: Mock заменяет реальную реализацию IConsumer<T>, позволяя тестировать только поведение класса, который его использует.
  2. Проверка вызовов: Убедитесь, что метод Consume вызывается ожидаемое количество раз (Times.Once, Times.Never).
  3. Проверка аргументов: Используйте It.Is<T>() для точной проверки переданных данных.
  4. Тестирование исключений: Можно настроить mock на выброс исключения и проверить реакцию основного кода.
// Пример теста на обработку исключения от Consumer
[Test]
public void Process_ShouldLogError_WhenConsumerThrows()
{
    var mockConsumer = new Mock<IConsumer<Order>>();
    mockConsumer.Setup(c => c.Consume(It.IsAny<Order>()))
                .Throws(new InvalidOperationException("Queue is full"));
    var mockLogger = new Mock<ILogger<OrderProcessor>>();
    var processor = new OrderProcessor(mockConsumer.Object, mockLogger.Object);

    Assert.Throws<InvalidOperationException>(() => processor.Process(new Order()));
    mockLogger.Verify(l => l.LogError(It.IsAny<string>()), Times.Once);
}