Mocks, stubs and fakes in C# unit tests: mocking with NSubstitute and Moq in xUnit

xUnit Testing & CI/CD in .NET November 13, 2026 csharp

Stub: returns canned answers (e.g.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// ======================================================================
// Q: Mocks, stubs and fakes? How do you mock with NSubstitute or Moq?
//
// - Stub: returns canned answers (e.g. "the ERP responds 4500100001").
// - Mock: you verify it was called with specific arguments (e.g. "an email was
//   sent").
// - Fake: a simple working implementation (in-memory repository,
//   FakeTimeProvider).
// - Don't mock what you don't own (DbContext, HttpClient). Mock your own
//   abstractions and cover the external ones with integration tests.
// - Over-mocking: if a test has 8 mocks and checks the order of calls, it tests
//   the implementation and breaks on every refactoring. Prefer fakes and
//   asserting on the outcome.
// ======================================================================

[Fact]
public async Task Rejected_by_erp_releases_stock_and_rejects_order()
{
    // Arrange
    var order = new OrderBuilder().WithLine("OLD-001", 3).Placed().Build();
    var orders = Substitute.For<IOrderRepository>();
    orders.GetForSystemAsync(order.Id, Arg.Any<CancellationToken>()).Returns(order);   // stub

    var erp = Substitute.For<IErpClient>();
    erp.SendOrderAsync(order, Arg.Any<CancellationToken>())
       .ThrowsAsync(new ErpRejectedException("Material OLD-001 is discontinued"));

    var stock = Substitute.For<IStockService>();
    var handler = new SyncOrderToErpHandler(orders, erp, stock, /* ... */);

    // Act
    await handler.Handle(new SyncOrderToErp(order.Id), CancellationToken.None);

    // Assert
    Assert.Equal(OrderStatus.Rejected, order.Status);
    await stock.Received(1).ReleaseAsync("OLD-001", 3, Arg.Any<CancellationToken>());   // mock
}

// The same with Moq
var erpMock = new Mock<IErpClient>();
erpMock.Setup(e => e.SendOrderAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()))
       .ReturnsAsync(new ErpOrderResult("4500100001"));
erpMock.Verify(e => e.SendOrderAsync(order, It.IsAny<CancellationToken>()), Times.Once);