Intermediate
Testing Smart Contracts — Hands-on lab
Build confidence before deploying.
Developer Intermediate
5/6 — Testing Smart Contracts — Hands-on lab
Show your work Build fixtures to avoid duplication, abuse cheatcodes to simulate adversaries, and measure coverage/gas. Document what each suite proves so reviewers and auditors gain confidence.
Trusted third parties are security holes.
Edge coverage Include tests for paused states, zero/overflow values, boundary timestamps, and unauthorized senders. Assert custom errors to keep intent clear.
Automation Add scripts to run the whole suite with coverage and gas reports locally and in CI. Fail builds on test or lint errors to keep standards high.
What to write
- Fixture + unit/revert
Baseline Foundry test with success + revert + invariant.contract TokenTest is Test { Token token; function setUp() public { token = new Token(); token.mint(address(this), 100 ether); } function testTransfer() public { token.transfer(address(1), 1 ether); assertEq(token.balanceOf(address(1)), 1 ether); } function testFailTransferInsufficient() public { vm.expectRevert(); token.transfer(address(1), 1_000 ether); } function testFuzzTransfer(uint128 amt) public { vm.assume(amt <= 100 ether); token.transfer(address(1), amt); assertEq(token.balanceOf(address(this)), 100 ether - amt); } function invariant_totalSupplyConstant() public { assertEq(token.totalSupply(), 100 ether); } }Covers happy path, failure, fuzz, and invariant in one suite.
- Adversary simulation
Cheatcodes for pranks/warp/roll.function testPauseBlocksTransfers() public { vm.prank(admin); token.pause(); vm.expectRevert(); token.transfer(address(1), 1 ether); } function testWarpAffectsVesting() public { vm.warp(block.timestamp + 30 days); uint256 releasable = vesting.releasable(user); assertGt(releasable, 0); }Covers pause/role guards and time-based logic with cheatcodes.
- CI command
Run coverage/gas every PR.forge test --gas-report --ffi --fail-fast # or hardhat npm run test -- --coverageMake CI a gate: tests, coverage, and gas diffs on every PR.
Steps
- Set up Hardhat or:Foundry with fixtures/snapshots. Write unit tests for core functions with success + revert paths.
- Add event/revert assertions (withArgs,:Custom errors). Test boundary values (zero, max uint, paused states).
- Write fuzz tests for:Math-heavy code (transfers, allowances, AMM formulas). Add invariant/property tests (totalSupply conservation, balance sums).
- Simulate adversaries with cheatcodes:Prank different senders, warp time, roll blocks, manipulate balances to test edge cases.
- Run coverage; identify untested:Branches; add tests to close gaps. Track gas where relevant (Forge gas snapshots or Hardhat gas reporter).
- Document how to run:Tests, what each suite covers, and known limitations. Add to CI with status checks.