← Назад к вопросам

Что такое NSubstitute?

1.0 Junior🔥 201 комментариев
#Основы C# и .NET

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое NSubstitute?

NSubstitute — это современная, легковесная и предельно удобная библиотека для создания мок-объектов (mock objects) в тестировании приложений на C#. Она относится к категории фреймворков подмены зависимостей (substitution frameworks) и является одной из самых популярных альтернатив более классическим инструментам, таким как Moq или Rhino Mocks. Её ключевая философия — предоставление максимально читаемого, лаконичного и выразительного синтаксиса на основе арранжирования через вызовы методов (arrange via method calls), а не через сложные API настройки.

Ключевые концепции и особенности

1. Создание подмен (Substitutes)

NSubstitute позволяет создавать подмены для интерфейсов, классов (с виртуальными членами) и даже делегатов. Основной метод — Substitute.For<T>().

// Создание подмены для интерфейса
var calculator = Substitute.For<ICalculator>();

// Создание подмены для класса с виртуальными методами
var repository = Substitute.For<Repository>();

2. Настройка возвращаемых значений (Stubbing)

Библиотека использует очень естественный синтаксис: чтобы задать возвращаемое значение метода, вы просто вызываете его и указываете, что должно вернуться.

calculator.Add(1, 2).Returns(3);
// Теперь при вызове calculator.Add(1, 2) вернётся 3

// Можно задавать разные возвращаемые значения для разных аргументов
calculator.Add(Arg.Any<int>(), Arg.Is<int>(x => x > 10)).Returns(999);

3. Проверка вызовов (Verification)

NSubstitute позволяет проверять, был ли вызван конкретный метод, с какими аргументами и сколько раз.

// Проверка, что метод был вызван хотя бы один раз
calculator.Received().Add(1, 2);

// Проверка точного количества вызовов
calculator.Received(3).PowerOn();

// Проверка, что метод НЕ был вызван
calculator.DidNotReceive().Reset();

4. Аргументные спецификаторы (Argument Matchers)

Это мощный механизм для гибкого сопоставления аргументов при настройке и проверке:

  • Arg.Any<T>() — любой аргумент типа T
  • Arg.Is<T>(выражение) — аргумент, удовлетворяющий условию
  • Arg.Compat — для устаревшего кода
// Любые строковые аргументы
service.Process(Arg.Any<string>()).Returns(true);

// Только строки длиннее 5 символов
service.Validate(Arg.Is<string>(s => s.Length > 5)).Returns(true);

5. Работа со свойствами и событиями

NSubstitute полностью поддерживает подмену свойств (включая индексаторы) и событий.

// Настройка свойства
calculator.Mode.Returns("Scientific");
calculator[0].Returns(100); // Индексатор

// Подписка и вызов события
var eventRaised = false;
calculator.PoweredOn += (sender, args) => eventRaised = true;
calculator.PoweredOn += Raise.Event(); // Имитация вызова события

Преимущества NSubstitute

Читаемость и выразительность

Синтаксис NSubstitute напоминает естественные предложения на английском: calculator.Add(1, 2).Returns(3) читается как "calculator.Add(1, 2) возвращает 3". Это делает тесты самодокументируемыми.

Минималистичный API

Библиотека имеет небольшой, хорошо продуманный API. Всё необходимое можно сделать с помощью нескольких основных методов: Returns(), Received(), DidNotReceive(), Arg.

Безопасность типов

Благодаря строгой типизации C# и продуманному дизайну, многие ошибки конфигурации отлавливаются на этапе компиляции, а не во время выполнения тестов.

Гибкость настройки

Поддержка цепочек вызовов, последовательных возвращаемых значений, колбэков и исключений:

// Последовательные возвращаемые значения
counter.GetNext().Returns(1, 2, 3, 4);

// Вызов колбэка при вызове метода
repository.Save(Arg.Any<User>()).Returns(x => 
{
    var user = x.Arg<User>();
    user.Id = Guid.NewGuid();
    return true;
});

// Выброс исключения
service.Connect().Throws(new NetworkException());

Типичный сценарий использования

// Интерфейс зависимости
public interface IEmailService
{
    bool Send(string to, string subject, string body);
}

// Класс для тестирования
public class OrderProcessor
{
    private readonly IEmailService _emailService;
    
    public OrderProcessor(IEmailService emailService)
    {
        _emailService = emailService;
    }
    
    public void ProcessOrder(Order order)
    {
        // Логика обработки...
        _emailService.Send(order.CustomerEmail, "Order confirmed", "Your order was processed");
    }
}

// Тест с использованием NSubstitute
[Test]
public void ProcessOrder_SendsConfirmationEmail()
{
    // Arrange
    var emailService = Substitute.For<IEmailService>();
    var processor = new OrderProcessor(emailService);
    var testOrder = new Order { CustomerEmail = "test@example.com" };
    
    emailService.Send(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
                .Returns(true);
    
    // Act
    processor.ProcessOrder(testOrder);
    
    // Assert
    emailService.Received(1)
                .Send("test@example.com", "Order confirmed", Arg.Any<string>());
}

Сравнение с другими фреймворками

По сравнению с Moq, NSubstitute предлагает более лаконичный синтаксис (особенно для проверки вызовов — Received() вместо Verify()). В отличие от Rhino Mocks, NSubstitute имеет более современный и простой API. FakeItEasy близок по философии, но NSubstitute часто считают имеющим более интуитивный синтаксис для C#-разработчиков.

Ограничения

  1. Требует виртуальных членов для подмены классов (как и большинство современных мок-фреймворков, использующих динамические прокси)
  2. Нет поддержки моков sealed-классов (для этого нужны другие инструменты, вроде Microsoft Fakes или Pose)
  3. Иногда сложные сценарии могут требовать более низкоуровневого API, который в NSubstitute ограничен

Заключение

NSubstitute — это отличный выбор для модульного тестирования в экосистеме .NET, особенно когда ценится читаемость тестов и скорость их написания. Его элегантный API позволяет сосредоточиться на тестируемой логике, а не на деталях настройки тестовых двойников. Библиотека активно развивается, имеет хорошую документацию и сообщество, что делает её надёжным инструментом в арсенале современного C#-разработчика, практикующего Test-Driven Development и ответственный подход к качеству кода.