| name | foundry-testing |
| description | Write and review Foundry smart contract tests. Use when writing Solidity tests, creating test files, or reviewing test coverage. |
Foundry Testing Best Practices
Fuzz Testing Strategy
- Fuzz function inputs by default - When a test function has parameters, Foundry automatically fuzzes them
- Use
bound() over vm.assume - Prefer bound(value, min, max) to constrain inputs; vm.assume discards runs and wastes iterations
- Apply Equivalence Class Partitioning - Only fuzz parameters that affect the code path under test. If a parameter doesn't change behavior, use a fixed representative value
- Maximize bounds - Use the widest reasonable bounds to maximize coverage
// Good: bound with max range
amount = bound(amount, 1, type(uint96).max);
// Avoid: vm.assume wastes fuzz runs
vm.assume(amount > 0 && amount < type(uint96).max);
Test Organization
- Gas benchmarks use
_gas suffix - Enables filtering with forge test --mt gas
- Pattern:
test_foo_gas() calls test_foo(fixed, values) for deterministic gas measurement
- Tests can return values for composition and reuse
- Group tests by functionality with section headers
function test_stake_gas() public returns (uint256 depositId) {
depositId = test_stake(address(this), 1 ether, OPERATOR, 0, address(this));
}
function test_stake(
address depositor,
uint96 amount,
address operator,
uint256 commissionRate,
address beneficiary
) public givenOperator(operator, commissionRate) returns (uint256 depositId) {
// Implementation...
}
Modularity
- Extract setup as modifiers - e.g.,
givenOperator(operator, rate), givenSpaceHasPointedToOperator(space, operator)
- Decouple unit tests - Avoid complex dependencies; simpler tests are easier to fuzz
- Use harness contracts for testing internal functions
contract MyContractHarness is MyContractBase {
function internalFunction(uint256 x) external pure returns (uint256) {
return _internalFunction(x);
}
}
Coverage Requirements
- Verify events with
vm.expectEmit
- Verify reverts with
vm.expectRevert
- Test storage slots - Verify EIP-7201 namespaced storage constants
- Cover critical paths - All state transitions, edge cases, and error conditions
// Event verification
vm.expectEmit(address(contract));
emit SomeEvent(expectedArg1, expectedArg2);
contract.someFunction();
// Revert verification
vm.expectRevert(SomeError.selector);
contract.shouldRevert();
// Storage slot verification (EIP-7201)
function test_storageSlot() public pure {
bytes32 slot = keccak256(
abi.encode(uint256(keccak256("namespace.storage")) - 1)
) & ~bytes32(uint256(0xff));
assertEq(slot, MyStorage.STORAGE_SLOT);
}
Naming Convention
| Pattern |
Usage |
test_functionName(params) |
Fuzz test (default when has parameters) |
test_functionName_gas() |
Gas benchmark with fixed inputs |
test_functionName_revertIf_Condition |
Revert condition tests |
test_functionName_EdgeCase |
Specific edge case tests |
Reference Examples