Skip to content

Mocking in C# — Complete Guide

DodaTech Updated 2026-06-28 6 min read

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

  1. Over-mocking: Mocking everything makes tests brittle. Mock external dependencies (I/O, services) but test domain logic directly.

  2. Not verifying behavior: Setting up a mock without verifying that methods were called misses the purpose of mocking.

  3. Using mock objects in integration tests: Integration tests should use real implementations or fakes, not mocks.

  4. 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.

  5. Mocking value types or sealed classes: Moq can only mock interfaces or abstract/virtual members. Refactor to use interfaces for testability.

Practice Questions

  1. Write a test that uses Moq to verify a Caching service stores and retrieves values correctly.

  2. Create a mock for HttpClient using MockHttpMessageHandler and test a service that calls an external API.

  3. Write a test using NSubstitute that verifies a Repository method is called with specific parameters.

  4. Challenge: Implement a spy using Moq's Callback that records all calls to a method and verifies the call order.

FAQ

What is the difference between a mock and a stub?

A stub provides pre-programmed responses. A mock also verifies that specific interactions occurred. Moq objects can serve as both.

Can I mock static methods?

Moq cannot mock static methods. Use a wrapper interface or refactor to use dependency injection.

How do I mock HttpMessageHandler for HttpClient?

Use MockHttpMessageHandler from RichardSzalay.MockHttp library or create a DelegatingHandler mock.

Should I use Moq or NSubstitute?

Both are excellent. Moq has wider adoption and more features. NSubstitute has a simpler, more readable syntax.

Can I mock DbContext in Entity Framework Core?

Yes, but use an in-memory database provider for most tests. Mock DbContext only when you need to verify specific call patterns.

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