Testing
Compose tests are behavior-first and primarily written with Foundry fuzz tests.
Testing Philosophy
- BTT (Behaviour Tree Testing): specs in
test/trees/*.treeare 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.solfor setup and deployment.facet/fuzz/*.t.solfor facet-facing behaviour.mod/fuzz/*.t.solfor 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.solandtest/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_Scenariotest_RevertWhen_ConditiontestFuzz_FunctionName_ScenariotestFuzz_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.expectEmitwhen 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 testpasses locally.forge fmt --checkis clean.
For deeper reference and examples, see test/README.md in the repository root.