Moq для C# (.NET): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое Moq и зачем он нужен?

Moq (произносится как "Мок") — это популярная библиотека для создания mock-объектов (заглушек) в C# (.NET). Она является частью экосистемы модульного тестирования и используется для изоляции тестируемого кода от его зависимостей.

В реальных приложениях классы часто зависят от других классов: базы данных, веб-сервисов, файловой системы, API сторонних сервисов. При написании юнит-тестов мы не хотим тестировать эти внешние зависимости — мы хотим проверить только логику нашего класса. Moq позволяет создавать "фальшивые" (mock) объекты, которые имитируют поведение реальных зависимостей, но при этом полностью контролируются тестом.

Основные сценарии использования Moq:

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

Moq работает с интерфейсами и виртуальными методами классов. Это ключевое ограничение: вы не можете замокать статический метод или невиртуальный метод.

Установка Moq

Moq распространяется через NuGet — менеджер пакетов для .NET. Установить его можно несколькими способами:

Через .NET CLI (рекомендуется)

dotnet add package Moq

Через Package Manager Console (Visual Studio)

Install-Package Moq

Через NuGet Package Manager UI

В Visual Studio: ПКМ на проекте → Manage NuGet Packages → Поиск "Moq" → Установить

Требования: .NET Framework 4.5+ или .NET Core 2.0+ / .NET 5+

Быстрый старт: первый тест с Moq

Предположим, у нас есть интерфейс репозитория и сервис, который его использует:

public interface IUserRepository
{
    User GetUserById(int id);
    void SaveUser(User user);
}

public class UserService
{
    private readonly IUserRepository _repository;

    public UserService(IUserRepository repository)
    {
        _repository = repository;
    }

    public string GetUserName(int userId)
    {
        var user = _repository.GetUserById(userId);
        return user?.Name ?? "Unknown";
    }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Теперь напишем тест с использованием Moq:

using Moq;
using Xunit;

public class UserServiceTests
{
    [Fact]
    public void GetUserName_WhenUserExists_ReturnsUserName()
    {
        // Arrange
        var mockRepo = new Mock<IUserRepository>();
        mockRepo.Setup(repo => repo.GetUserById(1))
                .Returns(new User { Id = 1, Name = "Alice" });

        var service = new UserService(mockRepo.Object);

        // Act
        var result = service.GetUserName(1);

        // Assert
        Assert.Equal("Alice", result);
        mockRepo.Verify(repo => repo.GetUserById(1), Times.Once);
    }
}

Разбор примера:

  1. new Mock<IUserRepository>() — создаем mock-объект для интерфейса.
  2. .Setup(...) — настраиваем поведение: при вызове GetUserById(1) вернуть пользователя Alice.
  3. mockRepo.Object — получаем "фальшивый" объект, который передаем в сервис.
  4. mockRepo.Verify(...) — проверяем, что метод был вызван ровно один раз.

Основные методы и функции Moq

1. Mock<T>.Setup()

Что делает: Настраивает поведение метода или свойства mock-объекта. Определяет, что должно произойти при вызове определенного члена.

Параметры: Лямбда-выражение, указывающее член для настройки.

Пример:

var mock = new Mock<ICalculator>();
mock.Setup(calc => calc.Add(It.IsAny<int>(), It.IsAny<int>()))
    .Returns((int a, int b) => a + b);

2. Mock<T>.SetupGet() / SetupSet()

Что делает: Настраивает поведение getter'ов и setter'ов свойств.

Параметры: Лямбда для доступа к свойству.

Пример:

var mock = new Mock<ISettings>();
mock.SetupGet(s => s.Timeout)
    .Returns(5000);
mock.SetupSet(s => s.Timeout = It.IsAny<int>())
    .Callback<int>(value => Console.WriteLine($"Timeout set to {value}"));

3. Mock<T>.Returns()

Что делает: Задает возвращаемое значение для метода. Может возвращать фиксированное значение, результат функции или генерировать последовательность значений.

Параметры: Значение или функция, возвращающая значение.

Пример:

// Фиксированное значение
mock.Setup(x => x.GetCount()).Returns(42);

// Функция с аргументами
mock.Setup(x => x.Calculate(It.IsAny<int>()))
    .Returns((int n) => n * 2);

// Последовательность значений
mock.SetupSequence(x => x.GetNext())
    .Returns(1)
    .Returns(2)
    .Throws(new InvalidOperationException());

4. Mock<T>.Throws()

Что делает: Заставляет метод выбрасывать исключение при вызове. Используется для тестирования обработки ошибок.

Параметры: Исключение или тип исключения.

Пример:

mock.Setup(x => x.Connect("bad_url"))
    .Throws<HttpRequestException>();

// С конкретным исключением
mock.Setup(x => x.Connect("invalid"))
    .Throws(new ArgumentException("Invalid URL"));

5. Mock<T>.Verify()

Что делает: Проверяет, что метод был вызван (или не был вызван) с определенными аргументами и определенное количество раз. Это сердце поведенческого тестирования.

Параметры: Лямбда с вызовом метода, опционально Times и строка сообщения об ошибке.

Пример:

// Проверка, что метод вызван ровно 1 раз
mock.Verify(x => x.Save(It.IsAny<string>()), Times.Once);

// Проверка, что метод не вызывался
mock.Verify(x => x.Delete(It.IsAny<int>()), Times.Never);

// Проверка с конкретными аргументами
mock.Verify(x => x.Update("key", "value"), Times.AtLeastOnce);

// Проверка с кастомным сообщением об ошибке
mock.Verify(x => x.Process(), Times.Exactly(3), "Process should be called 3 times");

6. Mock<T>.Callback()

Что делает: Выполняет пользовательский код при вызове метода. Полезно для логирования, проверки аргументов или изменения состояния.

Параметры: Делегат или лямбда, которая будет выполнена.

Пример:

var capturedArgs = new List<string>();

mock.Setup(x => x.Log(It.IsAny<string>()))
    .Callback<string>(msg => capturedArgs.Add(msg));

mock.Object.Log("First");
mock.Object.Log("Second");

Assert.Equal(2, capturedArgs.Count);
Assert.Equal("First", capturedArgs[0]);

7. It.IsAny<T>()

Что делает: Сопоставляется с любым значением указанного типа. Используется в Setup и Verify для игнорирования конкретных аргументов.

Параметры: Тип T.

Пример:

// Любой int
mock.Setup(x => x.Add(It.IsAny<int>(), 5)).Returns(10);

// Любая строка, не null
mock.Setup(x => x.Find(It.IsNotNull<string>())).Returns(true);

// Любое число больше 100
mock.Setup(x => x.Process(It.Is<int>(n => n > 100))).Returns("Big");

8. It.IsInRange<T>()

Что делает: Сопоставляется со значениями в заданном диапазоне (включительно или исключительно).

Параметры: Нижняя граница, верхняя граница, Range.Inclusive или Range.Exclusive.

Пример:

mock.Setup(x => x.SetVolume(It.IsInRange(0, 100, Range.Inclusive)))
    .Returns(true);

mock.Setup(x => x.SetVolume(It.IsInRange(-10, -1, Range.Exclusive)))
    .Throws<ArgumentOutOfRangeException>();

9. Mock<T>.Object

Что делает: Возвращает экземпляр mock-объекта, который реализует интерфейс T. Именно этот объект передается в тестируемый код.

Параметры: Нет.

Пример:

var mock = new Mock<IDatabase>();
IDatabase db = mock.Object; // Это "фальшивая" база данных
var service = new DataService(db);

10. MockBehavior

Что делает: Определяет, как mock ведет себя при вызове ненастроенных методов. Есть три режима: Loose (возвращает default значения), Strict (бросает исключение), Default (Loose).

Параметры: MockBehavior enum.

Пример:

// Strict: любой незапланированный вызов упадет с исключением
var strictMock = new Mock<IService>(MockBehavior.Strict);
strictMock.Setup(x => x.DoSomething()).Returns(true);
// strictMock.Object.DoSomethingElse(); // Exception!

// Loose: ненастроенные методы возвращают default
var looseMock = new Mock<IService>(MockBehavior.Loose);
int result = looseMock.Object.GetNumber(); // 0 (default for int)

11. Mock<T>.SetupAllProperties()

Что делает: Автоматически настраивает все свойства так, чтобы они работали как "заглушки" — можно устанавливать и получать значения без дополнительного Setup.

Параметры: Нет.

Пример:

public interface IConfig
{
    string Name { get; set; }
    int Port { get; set; }
}

var mock = new Mock<IConfig>();
mock.SetupAllProperties();
mock.Object.Name = "Test";
mock.Object.Port = 8080;

Assert.Equal("Test", mock.Object.Name);
Assert.Equal(8080, mock.Object.Port);

Реальный практический пример: тестирование сервиса отправки заказов

Рассмотрим более сложный сценарий: сервис обработки заказов, который зависит от репозитория, email-сервиса и логгера.

public interface IOrderRepository
{
    Order GetById(int id);
    void Update(Order order);
}

public interface IEmailService
{
    void SendEmail(string to, string subject, string body);
}

public interface ILogger
{
    void Log(string message);
}

public class Order
{
    public int Id { get; set; }
    public string CustomerEmail { get; set; }
    public decimal Total { get; set; }
    public bool IsProcessed { get; set; }
}

public class OrderService
{
    private readonly IOrderRepository _repo;
    private readonly IEmailService _email;
    private readonly ILogger _logger;

    public OrderService(I                    

Рекомендаций