evm-testing
Installation
SKILL.md
EVM Testing Patterns
Testing patterns for EVM smart contracts across Foundry (Solidity) and Hardhat (TypeScript). This skill focuses on how to write effective tests — not tool installation or CLI commands. See the foundry skill for Foundry setup, commands, and deployment.
What You Probably Got Wrong
- Foundry tests are Solidity, not JS -- Tests in Foundry are
.t.solfiles that extendforge-std/Test.sol. There is no Mocha, no Chai, no ethers.js. Every test is a Solidity function starting withtest. - Fuzz != invariant -- Fuzz tests run one function with random inputs. Invariant tests call random sequences of functions and assert properties that must always hold. They solve different problems.
vm.prankvsvm.startPrank--vm.prank(addr)only affects the NEXT external call. If your test makes multiple calls as the same address, usevm.startPrank(addr)...vm.stopPrank(). This is the #1 source of "why does my test pass when it shouldn't."- Hardhat uses Mocha/Chai, not Jest -- Hardhat tests use
describe/itfrom Mocha andexpectfrom Chai. Jest matchers (toBe,toEqual) do not exist. - Fork tests use real state --
vm.createSelectForkpulls actual mainnet storage. Whale balances change, contracts get upgraded, oracles update. Pin your block number or tests will flake. dealcheatcode for token balances --deal(address(token), user, amount)writes directly to the token's balance mapping. This works for standard ERC20s but can break tokens with rebasing, fee-on-transfer, or non-standard storage layouts. For those, impersonate a whale instead.expectRevertmust come BEFORE the call --vm.expectRevert()sets up an expectation for the next call. Placing it after the reverting call does nothing — the test reverts immediately andexpectRevertis never reached.expectEmitorder matters -- Callvm.expectEmit(), then emit the expected event shape, THEN execute the function that should emit it. Getting the order wrong silently passes or gives cryptic errors.