use-case
Implement the use case: $ARGUMENTS
Follow this exact sequence (TDD + Hexagonal Architecture):
Step 1: Write the failing use case test (RED)
Create the test file FIRST in tests/application/use-cases/.
tests/application/use-cases/$ARGUMENTS.test.ts
The test should:
- Use
describewith the use case name - Start with the happy path
- Reference any fakes it needs (they may not exist yet — that's fine)
- Run
pnpm run testand confirm it FAILS
Step 2: Identify needed dependencies
Before making the test pass, check if the use case needs:
- New Value Objects in
src/domain/models/ - New Entities/Aggregates in
src/domain/models/ - New Domain Errors in
src/domain/errors/ - New Output Ports in
src/domain/ports/
Reuse existing domain objects when possible.
Step 3: TDD the output port adapter (nested cycle)
If a NEW output port is needed, pause the use case cycle and TDD the adapter first:
3a. Define the output port interface
Create the interface in src/domain/ports/. This defines what the domain needs from the outside world.
3b. Write the contract test
Create a contract test that defines the expected behavior of ANY implementation of this port. Contract tests belong to the application layer — they define how the application expects the port to behave:
tests/application/ports/<PortName>.contract.ts
The contract is a reusable function that receives a factory. Add test cases for every method the use case will need.
3c. Write the fake test file
Create a test file that runs the contract with the fake:
tests/application/ports/Fake<PortName>.test.ts
Run pnpm run test — the contract tests should FAIL (fake doesn't exist yet).
3d. Implement the fake (GREEN)
Create the fake implementation. Fakes belong to the application layer — they are test doubles for the application's ports:
tests/application/ports/Fake<PortName>.ts
Write the minimum code to make the contract tests pass. Run pnpm run test to confirm.
Now resume the use case cycle.
Step 4: Implement domain objects
Create any needed Value Objects, Entities, Aggregates, and Domain Errors. Keep them minimal — only what the current use case test requires.
Step 5: Implement the use case (GREEN)
Create the implementation in src/application/use-cases/.
src/application/use-cases/$ARGUMENTS.ts
The implementation should:
- Receive output port dependencies via constructor injection
- Contain ONLY orchestration logic (business rules belong in the domain)
- Write the MINIMUM code to make the test pass
Run pnpm run test to verify the use case test passes.
Step 6: Add edge cases and error tests
Add tests for:
- Invalid inputs
- Domain error scenarios
- Boundary conditions
For each new test: RED → GREEN → refactor. If a new test requires a new method on an output port, go back to Step 3 and add it to the contract first.
Step 7: Refactor
With all tests green:
- Extract Value Objects if primitives are being passed around
- Ensure domain logic is in the domain layer, not the use case
- Check naming follows ubiquitous language
- Verify the contract test covers all repository behaviors used by the use case