Ответ
NSubstitute — это популярная, легковесная библиотека для .NET, предназначенная для создания тестовых заглушек (mocks, stubs, spies) с очень читаемым и лаконичным синтаксисом. Она является альтернативой Moq и часто выбирается за свой "естественный" стиль.
Основные возможности:
- Создание заместителей (substitutes) для интерфейсов и классов (с виртуальными членами).
- Настройка возвращаемых значений и исключений.
- Проверка вызовов (был ли вызван, сколько раз, с какими аргументами).
- Работа с
outиrefпараметрами.
Пример сравнения: тест сервиса уведомлений
public interface INotificationService
{
void SendEmail(string to, string subject, string body);
Task<bool> SendSmsAsync(string phoneNumber, string message);
}
public class OrderProcessor
{
private readonly INotificationService _notifier;
public OrderProcessor(INotificationService notifier) => _notifier = notifier;
public void ProcessOrder(Order order)
{
// ... логика обработки ...
_notifier.SendEmail(order.CustomerEmail, "Order Confirmed", $"Your order #{order.Id} is processed.");
}
}
[Test]
public void ProcessOrder_ShouldSendConfirmationEmail()
{
// Arrange
var notificationSub = Substitute.For<INotificationService>();
var processor = new OrderProcessor(notificationSub);
var testOrder = new Order { Id = 777, CustomerEmail = "test@example.com" };
// Act
processor.ProcessOrder(testOrder);
// Assert
// Проверяем, что SendEmail был вызван 1 раз с конкретными аргументами
notificationSub.Received(1).SendEmail(
"test@example.com",
"Order Confirmed",
"Your order #777 is processed."
);
// Проверяем, что асинхронный метод SendSmsAsync НЕ вызывался
notificationSub.DidNotReceive().SendSmsAsync(Arg.Any<string>(), Arg.Any<string>());
}
// Пример настройки возвращаемого значения
[Test]
public async Task CheckStatus_ShouldReturnTrue_WhenSmsSentSuccessfully()
{
var service = Substitute.For<INotificationService>();
// Настраиваем асинхронный метод на возврат true
service.SendSmsAsync("+79001234567", "Hello").Returns(Task.FromResult(true));
bool result = await service.SendSmsAsync("+79001234567", "Hello");
Assert.That(result, Is.True);
}
Плюсы NSubstitute:
- Читаемый синтаксис:
Received(),DidNotReceive(),Returns()выглядят как часть утверждения. - Умные сопоставления аргументов:
Arg.Any<string>(),Arg.Is<string>(s => s.Contains("test")). - Хорошая документация и активное сообщество.
Ответ 18+ 🔞
Давай я тебе на пальцах объясню, что за зверь такой этот NSubstitute, а то в документации всё заумно расписано, глаза сломаешь.
Представь, ты пишешь код для какого-нибудь интернет-магазина. Там есть класс, который заказы обрабатывает, и ему для работы нужен сервис отправки писем. Но тебе-то сейчас не письма реальные слать, тебе просто проверить, что твой код в принципе пытается это письмо отправить. Вот тут-то и пригождается эта библиотека.
NSubstitute — это, по сути, фабрика по производству муляжей, или, как умные говорят, «заглушек». Нужен тебе объект, который выглядит как INotificationService, ведёт себя как он, но на самом деле это пустышка, которую ты полностью контролируешь. Создаётся это чудо одной строчкой:
var fakeNotifier = Substitute.For<INotificationService>();
Вот и всё. У тебя теперь есть fakeNotifier — полная липа, но очень убедительная. Она умеет делать всё, что объявлено в интерфейсе, но по умолчанию ничего не делает и возвращает default значения.
А зачем это вообще надо? Да затем, чтобы не тащить в тесты всю вселенную! Не поднимать базу данных, не конфигурировать SMTP-сервер, не платить деньги за реальные SMS. Ты просто говоришь своему коду: «Вот тебе сервис уведомлений, работай с ним», — а сам втихую следишь, что и как он с этим сервисом делает.
Что она умеет, эта заглушка? Да почти всё, что душе угодно!
-
Настраивать ответы. Скажем, нужно, чтобы метод
SendSmsAsyncвсегда возвращалtrue. Легко!fakeNotifier.SendSmsAsync(Arg.Any<string>(), Arg.Any<string>()) .Returns(Task.FromResult(true));Видишь
Arg.Any<string>()? Это матчер. Он значит: «Насрать, какие аргументы придут, просто верниtrue». Можно и похитрее:Arg.Is<string>(s => s.StartsWith("+7"))— сработает только для российских номеров. -
Проверять, что её вызывали. Это главная фишка для тестов. Обработали заказ? Отлично. А теперь давай проверим, не обманывает ли нас код, и письмо таки отправилось.
// Act — код отработал... processor.ProcessOrder(testOrder); // Assert — а теперь проверяем заглушку fakeNotifier.Received(1).SendEmail( "test@example.com", "Order Confirmed", "Your order #777 is processed." );Метод
Received(1)— это прямо приказ: «Покажи, был ли вызов методаSendEmailровно ОДИН раз с вот такими точными аргументами». Если не был — тест упадёт с позором. Можно и наоборот проверить:fakeNotifier.DidNotReceive().SendSmsAsync(...)— чтобы убедиться, что SMS не ушла, когда не должна была. -
Ловить исключения. Хочешь проверить, как твой код поведёт себя, если сервис упадёт? Пожалуйста, настрой заглушку на выброс исключения.
fakeNotifier.SendEmail(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>()) .Throws(new SmtpException("Сервер сгорел"));
Чем она хороша?
- Читается, как обычная речь.
Received(1),DidNotReceive(),Returns()— это интуитивно. Не нужно выдумывать сложные конструкции верификации. - Не грузит лишней философией. Создал, настроил, проверил — и свободен. Никакой магии с деревьями выражений, как в некоторых других библиотеках.
- Стабильная и популярная. Документация отличная, сообщество большое, проблем с поиском ответов не будет.
Короче говоря, если ты пишешь тесты и устал городить ручные заглушки или воротить нос от синтаксиса других мок-библиотек — NSubstitute это твой выбор. Он делает сложные вещи простыми, а тесты — понятными даже тому, кто в проекте первый день. Берёшь и пользуешься, вопросов даже не возникает.