Test the core through the command handler — and keep it fast
Exercise behaviour through the natural entry point, fake the ports, and never reach for internals.
Here’s where Module 2 pays off. Remember the command handler — the single entry point for a behaviour, talking to the world only through ports. That makes it the perfect place to aim a test. You exercise the behaviour (invite a teammate) through its natural front door, and you assert on the outcome, not the internals. You’re testing the promise, exactly as the previous section demands.
But the handler loads and saves through a repository port — and a real database is slow, which would drag your fast loop down to a crawl. So you don’t use the real one. You implement the port with a test double: an InMemoryRepository that satisfies the same contract using a dictionary in memory. The handler can’t tell the difference — that’s the whole point of the port. Your test now runs in milliseconds and still exercises real business logic.
// the same handler, tested against a fake port
repo = InMemoryRepository(seededWith: workspaceAtSeatLimit)
handler = InviteTeammateHandler(repo)
handler.handle(InviteTeammate(workspaceId, "new@person.com"))
assert: invite was rejected — no seats remained
This is a deliberate stance, and worth naming as one: test behaviour through the entry point, fake the ports, never reach for internals. It keeps the fast rung of your ladder both fast and trustworthy — the combination that lets an agent lean on it.