Stub: returns canned answers (e.g.
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); |