Whetstone.
Testing to the Fusion barThe test stack and its idioms
Module 11 · Lesson 115 min

The test stack and its idioms

Four libraries, and you’ll recognise the shape from Jest even though the names differ. xUnit is the runner: [Fact] marks a test method (like it(...)). Shouldly is the assertion library: result.IsSuccess.ShouldBeTrue(), value.ShouldBe(60), Should.Throw<T>(...), readable and specific. NSubstitute is the mock library: Substitute.For<IRepo>() fakes an interface. AutoFixture builds test data so you don’t hand-construct objects whose exact values don’t matter.

A VO test (real FlightNumberVoTests, trimmed)
public class FlightNumberVoTests
{
    public class Create
    {
        [Fact]
        public void ValidValue_ReturnsSuccess()
        {
            var result = FlightNumberVo.Create("4088");
            result.IsSuccess.ShouldBeTrue();
            result.Value.Value.ShouldBe("4088");
        }

        [Fact]
        public void NullValue_ReturnsFailure()
        {
            var result = FlightNumberVo.Create(null);
            result.IsFailure.ShouldBeTrue();
            result.Errors.ShouldContain(e => e.Field == "Value" && e.Message.Contains("required"));
        }
    }
}
A use-case test with NSubstitute (real RevertDisruptionDecisionTests)
var repo = Substitute.For<IFlightDisruptionAggregateRepository>();
repo.LoadRevertSummary(TestFlightKey).Returns(
    new FlightRevertSummary(42, "4088", TestFlightKey, HasActiveDecision: true));
var useCase = new RevertDisruptionDecision(repo, () => FixedNow);   // fixed clock

var result = await useCase.Execute(new RevertDisruptionDecisionRequest(TestFlightKey));

result.IsSuccess.ShouldBeTrue();
await repo.Received(1).MarkAsReverted(42, TestFlightKey, FixedNow);   // verify the interaction
Practice

Try it yourself

Do

Test the VO you built

Write the test the way the team writes it, boundaries included.

  • For the BookingReferenceVo from Module 8, write a BookingReferenceVoTests class with a nested Create class
  • Add [Fact] tests: valid 6-char reference returns success, null returns failure, a 5-char (boundary miss) returns failure
  • Use Shouldly: result.IsSuccess.ShouldBeTrue(), result.Errors.ShouldContain(...)
  • Run dotnet test and confirm green
Done whenYou have a passing test class named after the type under test, covering the happy path and at least one boundary failure, using xUnit and Shouldly.
Quiz

How to fake a repository

Your use-case test needs a fake IFlightDisruptionAggregateRepository. What do you write?

  1. AA hand-rolled class StubRepo : IFlightDisruptionAggregateRepository implementing every method
  2. BSubstitute.For<IFlightDisruptionAggregateRepository>() and set up the methods you need with .Returns(...)
  3. CA real repository pointed at a test database
  4. DNothing, pass null
Show answer

Correct answer: B — Substitute.For<IFlightDisruptionAggregateRepository>() and set up the methods you need with .Returns(...)

Anti-pattern #11: use NSubstitute (Substitute.For<I>()), which generates the fake from the interface so it can never drift from the contract. Hand-rolled stubs are extra maintenance the moment the interface changes.

Sign in to track your progress →