Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое 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>()— любой аргумент типа TArg.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#-разработчиков.
Ограничения
- Требует виртуальных членов для подмены классов (как и большинство современных мок-фреймворков, использующих динамические прокси)
- Нет поддержки моков sealed-классов (для этого нужны другие инструменты, вроде Microsoft Fakes или Pose)
- Иногда сложные сценарии могут требовать более низкоуровневого API, который в NSubstitute ограничен
Заключение
NSubstitute — это отличный выбор для модульного тестирования в экосистеме .NET, особенно когда ценится читаемость тестов и скорость их написания. Его элегантный API позволяет сосредоточиться на тестируемой логике, а не на деталях настройки тестовых двойников. Библиотека активно развивается, имеет хорошую документацию и сообщество, что делает её надёжным инструментом в арсенале современного C#-разработчика, практикующего Test-Driven Development и ответственный подход к качеству кода.