| name | testing |
| description | Smart contract testing with Foundry — unit tests, fuzz testing, fork testing, invariant testing. Use when writing tests for a smart contract. |
Smart Contract Testing
What You Probably Got Wrong
You test getters and trivial functions. Testing that name() returns the name is worthless. Test edge cases, failure modes, and economic invariants — the things that lose money when they break.
You don't fuzz. forge test finds the bugs you thought of. Fuzzing finds the ones you didn't. If your contract does math, fuzz it. If it handles user input, fuzz it. If it moves value, definitely fuzz it.
You don't fork-test. If your contract calls Uniswap, Aave, or any external protocol (verified addresses: addresses/SKILL.md), test against their real deployed contracts on a fork. Mocking them hides integration bugs that only appear with real state.
You write tests that mirror the implementation. Testing that deposit(100) sets balance[user] = 100 is tautological — you're testing that Solidity assignments work. Test properties: "after deposit and withdraw, user gets their tokens back." Test invariants: "total deposits always equals contract balance."
You skip invariant testing for stateful protocols. If your contract has multiple interacting functions that change state over time (vaults, AMMs, lending), you need invariant tests. Unit tests check one path; invariant tests check that properties hold across thousands of random sequences.
Unit Testing with Foundry
Test File Structure
// test/MyContract.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import {MyToken} from "../src/MyToken.sol";
contract MyTokenTest is Test {
MyToken public token;
address public alice = makeAddr("alice");
address public bob = makeAddr("bob");
function setUp() public {
token = new MyToken("Test", "TST", 1_000_000e18);
// Give alice some tokens for testing
token.transfer(alice, 10_000e18);
}
function test_TransferUpdatesBalances() public {
vm.prank(alice);
token.transfer(bob, 1_000e18);
assertEq(token.balanceOf(alice), 9_000e18);
assertEq(token.balanceOf(bob), 1_000e18);
}
function test_TransferEmitsEvent() public {
vm.expectEmit(true, true, false, true);
emit Transfer(alice, bob, 500e18);
vm.prank(alice);
token.transfer(bob, 500e18);
}
function test_RevertWhen_TransferExceedsBalance() public {
vm.prank(alice);
vm.expectRevert();
token.transfer(bob, 999_999e18); // More than alice has
}
function test_RevertWhen_TransferToZeroAddress() public {
vm.prank(alice);
vm.expectRevert();
token.transfer(address(0), 100e18);
}
}