Skip to main content

Testing

Compose tests are behavior-first and primarily written with Foundry fuzz tests.

Testing Philosophy

  • BTT (Behaviour Tree Testing): specs in test/trees/*.tree are the source of truth.
  • Facet/mod parity: test both facet surfaces and modifier/module behaviour where applicable.
  • No test-only code in production: keep test helpers under test/ (harnesses, storage, base fixtures).
  • Behaviour-oriented naming: test names should read like tree leaves and scenarios.

Test Layout

test/
├── Base.t.sol # shared base setup (users/defaults)
├── trees/ # behaviour specifications
│ ├── ERC20.tree
│ ├── ERC721.tree
│ ├── ERC1155.tree
│ ├── ERC6909.tree
│ ├── AccessControl.tree
│ ├── Owner.tree
│ ├── ERC165.tree
│ ├── Royalty.tree
│ └── NonReentrancy.tree
├── unit/
│ ├── token/
│ ├── access/
│ ├── interfaceDetection/
│ └── libraries/
└── utils/
├── harnesses/ # expose module/internal behaviour for tests
└── storage/ # vm.load/vm.store storage utilities

Within each feature area, tests are usually split into:

  • *FacetBase.t.sol / *ModBase.t.sol for setup and deployment.
  • facet/fuzz/*.t.sol for facet-facing behaviour.
  • mod/fuzz/*.t.sol for module/modifier-facing behaviour.

How to Add or Update Tests

1) Start from the tree

Find the relevant tree file (for example test/trees/ERC20.tree) and identify the branch/leaves your change affects.

If behaviour is new, add the branch/leaves first.

2) Reuse existing fixture layers

  • Extend the nearest domain base test (*Base.t.sol) rather than building setup from scratch.
  • Prefer existing users/defaults/constants from test/Base.t.sol and test/utils/*.
  • Label deployed contracts in setup (vm.label) for readable traces.

3) Keep tests tree-aligned

  • Add the BTT reference comment at the top of test contracts:
/**
* @dev BTT spec: test/trees/ERC20.tree
*/
  • Use naming patterns that match behaviour:
    • test_FunctionName_Scenario
    • test_RevertWhen_Condition
    • testFuzz_FunctionName_Scenario
    • testFuzz_ShouldRevert_Reason

4) Prefer robust fuzz setup

  • Use bound(...) for numeric ranges.
  • Use vm.assume(...) only when needed for discrete constraints.
  • Assert custom errors with selector-encoded revert data.
  • Assert events with vm.expectEmit when the behaviour specifies emissions.

5) Use harnesses and storage utils (not production hooks)

  • Use test/utils/harnesses/** to expose internals or module surfaces in tests.
  • Use test/utils/storage/** (vm.load / vm.store) for layout-sensitive setup/assertions.
  • Do not add test-only getters or hooks to src/.

Commands

# run entire suite
forge test

# run one area while iterating
forge test --match-path "test/unit/token/ERC20/**"

# run one specific test file
forge test --match-path "test/unit/token/ERC20/Transfer/facet/fuzz/transfer.t.sol"

# run one specific test function
forge test --match-test "test_RevertWhen_TransferInsufficientBalance"

# optional detailed logs while debugging
forge test -vvv

# gas regression checks where relevant
forge test --gas-report

Pre-PR Checklist

  • Behaviour tree updated (if behaviour changed).
  • Tests added/updated for both success and revert paths.
  • No test-only logic added to production contracts.
  • forge test passes locally.
  • forge fmt --check is clean.

For deeper reference and examples, see test/README.md in the repository root.

Newsletter

Get notified about releases, feature announcements, and technical deep-dives on building smart contracts with Compose.

No spam. Unsubscribe anytime.