Mocking in C# — Complete Guide
In this tutorial, you will learn about Mocking in C#. We cover key concepts, practical examples, and best practices to help you master this topic.
Hook
Unit tests should test one thing in isolation. When your code depends on databases, APIs, or file systems, mocking lets you replace those dependencies with controlled, predictable substitutes. C# developers use mocking frameworks to verify behavior, stub return values, and assert that interactions happen correctly.
Learning Path
graph LR A[Mocking] --> B[Moq] A --> C[NSubstitute] B --> D[Setup & Returns] B --> E[Verification] D --> F[Test Doubles] style A fill:#4a90d9,color:#fff style B fill:#4a90d9,color:#fff style C fill:#4a90d9,color:#fff style D fill:#4a90d9,color:#fff style E fill:#4a90d9,color:#fff style F fill:#4a90d9,color:#fff
Why Mocking
Consider a service that sends emails. Testing it would require an actual SMTP server -- unreliable and slow.
public interface IEmailService
{
Task SendEmailAsync(string to, string subject, string body);
bool IsValidEmail(string email);
}
public class NotificationService
{
private readonly IEmailService _emailService;
private readonly ILogger<NotificationService> _logger;
public NotificationService(IEmailService emailService, ILogger<NotificationService> logger)
{
_emailService = emailService;
_logger = logger;
}
public async Task NotifyUser(string email, string message)
{
if (!_emailService.IsValidEmail(email))
{
_logger.LogWarning("Invalid email: {Email}", email);
throw new ArgumentException("Invalid email");
}
await _emailService.SendEmailAsync(email, "Notification", message);
_logger.LogInformation("Email sent to {Email}", email);
}
}
Mocking with Moq
Moq is the most popular mocking framework for .NET.
using Moq;
using Xunit;
public class NotificationServiceTests
{
[Fact]
public async Task NotifyUser_ShouldSendEmail_WhenEmailIsValid()
{
// Arrange
var mockEmail = new Mock<IEmailService>();
var mockLogger = new Mock<ILogger<NotificationService>>();
mockEmail.Setup(e => e.IsValidEmail(It.IsAny<string>()))
.Returns(true);
var service = new NotificationService(mockEmail.Object, mockLogger.Object);
// Act
await service.NotifyUser("test@example.com", "Hello!");
// Assert
mockEmail.Verify(e => e.SendEmailAsync(
"test@example.com",
"Notification",
"Hello!"
), Times.Once);
}
[Fact]
public async Task NotifyUser_ShouldThrow_WhenEmailIsInvalid()
{
// Arrange
var mockEmail = new Mock<IEmailService>();
var mockLogger = new Mock<ILogger<NotificationService>>();
mockEmail.Setup(e => e.IsValidEmail(It.IsAny<string>()))
.Returns(false);
var service = new NotificationService(mockEmail.Object, mockLogger.Object);
// Act & Assert
await Assert.ThrowsAsync<ArgumentException>(() =>
service.NotifyUser("invalid", "test"));
mockEmail.Verify(e => e.SendEmailAsync(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()
), Times.Never);
}
}
Moq Setup Patterns
[Fact]
public void MoqSetupExamples()
{
var mock = new Mock<IRepository>();
// Return a value
mock.Setup(r => r.GetById(1)).Returns(new Product { Id = 1, Name = "Test" });
// Return based on input
mock.Setup(r => r.GetById(It.IsAny<int>()))
.Returns<int>(id => new Product { Id = id });
// Async return
mock.Setup(r => r.GetAllAsync())
.ReturnsAsync(new List<Product> { new Product { Id = 1 } });
// Out parameter
mock.Setup(r => r.TryGetValue("key", out It.Ref<object>.IsAny))
.Returns((string k, out object v) =>
{
v = "found";
return true;
});
// Callback
string? capturedName = null;
mock.Setup(r => r.Update(It.IsAny<Product>()))
.Callback<Product>(p => capturedName = p.Name);
// Exceptions
mock.Setup(r => r.Delete(It.IsAny<int>()))
.Throws<InvalidOperationException>();
// Property
mock.SetupProperty(r => r.ConnectionString, "default");
// Loose vs Strict
mock.DefaultValue = DefaultValue.Mock;
}
Verification in Moq
Verify that methods were called with expected arguments and counts.
[Fact]
public void VerificationExamples()
{
var mock = new Mock<IEmailService>();
// Verify exact call
mock.Verify(e => e.SendEmailAsync("a@b.com", "Sub", "Body"),
Times.Once);
// Verify with matchers
mock.Verify(e => e.SendEmailAsync(
It.Is<string>(s => s.Contains("@")),
It.IsAny<string>(),
It.IsNotNull<string>()
), Times.AtLeastOnce);
// Verify never called
mock.Verify(e => e.SendEmailAsync(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()
), Times.Never);
// Verify no calls at all
mock.VerifyNoOtherCalls();
}
NSubstitute
NSubstitute offers a more concise syntax.
using NSubstitute;
using Xunit;
public class NSubstituteTests
{
[Fact]
public async Task NotifyUser_ShouldSendEmail()
{
// Arrange
var emailService = Substitute.For<IEmailService>();
var logger = Substitute.For<ILogger<NotificationService>>();
emailService.IsValidEmail(Arg.Any<string>()).Returns(true);
var service = new NotificationService(emailService, logger);
// Act
await service.NotifyUser("test@example.com", "Hello!");
// Assert
await emailService.Received(1).SendEmailAsync(
"test@example.com", "Notification", "Hello!");
logger.Received(1).Log(
Arg.Is<LogLevel>(l => l == LogLevel.Information),
Arg.Is<EventId>(e => e.Id == 0),
Arg.Is<string>(s => s.Contains("Email sent")),
null,
Arg.Any<Func<string, Exception, string>>());
}
}
Test Doubles
Understanding the different types of test doubles.
// Stub: provides predetermined responses
var stub = new Mock<IWeatherService>();
stub.Setup(w => w.GetTemperature("London")).Returns(22);
// Mock: verifies interactions
var mock = new Mock<IEmailService>();
// ... use mock.Verify later
// Fake: simplified working implementation
public class FakeUserRepository : IUserRepository
{
private readonly List<User> _users = new();
public Task<User?> GetByIdAsync(int id) =>
Task.FromResult(_users.FirstOrDefault(u => u.Id == id));
}
// Dummy: passed around but never used
var dummyLogger = Mock.Of<ILogger<MyService>>();
Common Mistakes
Over-mocking: Mocking everything makes tests brittle. Mock external dependencies (I/O, services) but test domain logic directly.
Not verifying behavior: Setting up a mock without verifying that methods were called misses the purpose of mocking.
Using mock objects in integration tests: Integration tests should use real implementations or fakes, not mocks.
Ignoring strict vs loose behavior: By default, Moq is loose (calls without setup return default values). Use strict mocks when you want to enforce exact behavior.
Mocking value types or sealed classes: Moq can only mock interfaces or abstract/virtual members. Refactor to use interfaces for testability.
Practice Questions
Write a test that uses Moq to verify a Caching service stores and retrieves values correctly.
Create a mock for
HttpClientusingMockHttpMessageHandlerand test a service that calls an external API.Write a test using NSubstitute that verifies a Repository method is called with specific parameters.
Challenge: Implement a spy using Moq's
Callbackthat records all calls to a method and verifies the call order.
FAQ
Mini Project: Order Processing Mocking
Test an order processing system with mocked dependencies.
using Moq;
using Xunit;
public interface IPaymentGateway
{
Task<bool> ChargeAsync(string cardNumber, decimal amount);
}
public interface IInventoryService
{
Task<bool> CheckStockAsync(int productId, int quantity);
Task ReserveAsync(int productId, int quantity);
}
public class OrderProcessor
{
private readonly IPaymentGateway _payment;
private readonly IInventoryService _inventory;
private readonly ILogger<OrderProcessor> _logger;
public OrderProcessor(IPaymentGateway payment, IInventoryService inventory,
ILogger<OrderProcessor> logger)
{
_payment = payment;
_inventory = inventory;
_logger = logger;
}
public async Task<bool> ProcessOrderAsync(Order order)
{
_logger.LogInformation("Processing order {OrderId}", order.Id);
if (!await _inventory.CheckStockAsync(order.ProductId, order.Quantity))
{
_logger.LogWarning("Insufficient stock for product {ProductId}", order.ProductId);
return false;
}
var charged = await _payment.ChargeAsync(order.CardNumber, order.Total);
if (!charged)
{
_logger.LogError("Payment failed for order {OrderId}", order.Id);
return false;
}
await _inventory.ReserveAsync(order.ProductId, order.Quantity);
_logger.LogInformation("Order {OrderId} completed successfully", order.Id);
return true;
}
}
public class Order
{
public int Id { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public decimal Total { get; set; }
public string CardNumber { get; set; } = "";
}
public class OrderProcessorTests
{
[Fact]
public async Task ProcessOrderAsync_ShouldSucceed_WhenStockAndPaymentOk()
{
var payment = new Mock<IPaymentGateway>();
var inventory = new Mock<IInventoryService>();
var logger = new Mock<ILogger<OrderProcessor>>();
payment.Setup(p => p.ChargeAsync(It.IsAny<string>(), It.IsAny<decimal>()))
.ReturnsAsync(true);
inventory.Setup(i => i.CheckStockAsync(It.IsAny<int>(), It.IsAny<int>()))
.ReturnsAsync(true);
var processor = new OrderProcessor(payment.Object, inventory.Object, logger.Object);
var order = new Order { Id = 1, ProductId = 42, Quantity = 2, Total = 49.99m, CardNumber = "4111-1111-1111-1111" };
var result = await processor.ProcessOrderAsync(order);
Assert.True(result);
inventory.Verify(i => i.ReserveAsync(42, 2), Times.Once);
payment.Verify(p => p.ChargeAsync("4111-1111-1111-1111", 49.99m), Times.Once);
}
[Fact]
public async Task ProcessOrderAsync_ShouldFail_WhenOutOfStock()
{
var payment = new Mock<IPaymentGateway>();
var inventory = new Mock<IInventoryService>();
var logger = new Mock<ILogger<OrderProcessor>>();
inventory.Setup(i => i.CheckStockAsync(It.IsAny<int>(), It.IsAny<int>()))
.ReturnsAsync(false);
var processor = new OrderProcessor(payment.Object, inventory.Object, logger.Object);
var result = await processor.ProcessOrderAsync(
new Order { Id = 2, ProductId = 99, Quantity = 1, Total = 10m, CardNumber = "test" });
Assert.False(result);
payment.Verify(p => p.ChargeAsync(It.IsAny<string>(), It.IsAny<decimal>()), Times.Never);
}
}
Test Output:
Passed OrderProcessorTests.ProcessOrderAsync_ShouldSucceed_WhenStockAndPaymentOk
Passed OrderProcessorTests.ProcessOrderAsync_ShouldFail_WhenOutOfStock
Mocking is essential for writing focused, fast, and reliable unit tests in C#. By using Moq or NSubstitute with the .NET Dependency Injection pattern, you can test complex scenarios without setting up real infrastructure.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro