π§ͺTests
forge test --summary
forge test -vvv
forge test --match-test <SPECIFIC_TEST_FUNCTION_NAME>
forge test --match-contract <SPECIFIC_CONTRACT_NAME>
forge test --debug <SPECIFIC_TEST_FUNCTION_NAME> # h to show/hide help
Tests are written in Solidity. If the test function reverts, the test fails, otherwise it passes.
Unit
Testing a specific part of our code
Integration
Testing how our code works with other parts of our code
Forked
Testing our code on a simulated real environment
Staging
Testing our code in a real environment that is not prod
Fuzz
Testing our code against unexpected/random inputs to find bugs
Writing Tests
DSTest provides basic logging and assertion functionality. To get access to the functions, import forge-std/Test.sol
and inherit from Test
in your test contract:
import "forge-std/Test.sol";
pragma solidity 0.8.10;
import "forge-std/Test.sol";
contract ContractBTest is Test {
uint256 testNumber;
string string1;
string string2;
// An optional function invoked before each test case is run.
function setUp() public {
testNumber = 42;
string1 = ABC;
string2 = XYZ;
}
// Functions prefixed with test are run as a test case.
function test_NumberIs42() public {
assertEq(testNumber, 42);
}
// Inverse test prefix - if the function does not revert, the test fails.
function testFail_Subtract43() public {
testNumber -= 43;
}
function test_CompareStrings() public {
assertEq(
keccak256(abi.encodePacked(string1)),
keccak256(abi.encodePacked(string2))
);
}
}
Test functions must have either external
or public
visibility. Functions declared as internal
or private
won't be picked up by Forge, even if they are prefixed with test
.
Naming Convention
Shared test setups
If there are multiple tests that all have the same initial setup configuration, use a helper contract to reduce code duplication
abstract contract TestHelperContract is Test, {
address constant IMPORTANT_ADDRESS = 0x543d...;
SomeContract someContract;
constructor() {...}
}
contract MyContractTest is TestHelperContract {
function setUp() public {
someContract = new SomeContract(0, IMPORTANT_ADDRESS);
...
}
}
contract MyOtherContractTest is TestHelperContract {
function setUp() public {
someContract = new SomeContract(1000, IMPORTANT_ADDRESS);
...
}
}
Custom Error Reverts
Specify which custom error is expected on revert.
If the error returns a value, the test needs to encode the value using
abi.encodeWithSelector
to ensure the test passes.
contract DSC {
error DSC__MintNotZeroAddress();
error DSCEngine__HealthFactorIsBelowMinimum(uint256 healthFactor);
function _revertIfHealthFactorIsBroken(address user) internal view {
uint256 userHealthFactor = _healthFactor(user);
if (userHealthFactor < MIN_HEALTH_FACTOR) {
revert DSCEngine__HealthFactorIsBelowMinimum(userHealthFactor);
}
}
}
contract DSCTest is Test {
function test_CantMintToZeroAddress() public {
vm.prank(dsc.owner());
vm.expectRevert(DSC.DSC__MintNotZeroAddress.selector);
dsc.mint(address(0), 100);
}
function test_MintFailsIfHealthFactorIsBroken() public depositedCollateral {
uint256 healthFactor = 2;
bytes memory encodedRevert = abi.encodeWithSelector(
DSCEngine.DSCEngine__HealthFactorIsBelowMinimum.selector,
healthFactor
);
vm.startPrank(USER);
vm.expectRevert(encodedRevert);
dscEngine.mintDsc(usdAmount);
vm.stopPrank();
}
}
Event Testing
Unit Testing
forge test --summary
forge test -vvv
forge test --mc <SPECIFIC_TEST_CONTRACT_NAME>
forge test --mt <SPECIFIC_TEST_FUNCTION_NAME>
forge test --fork-url <RPC_URL>
Integration Testing
TODO
Forked Testing
To run all tests in a forked environment, such as a forked Ethereum mainnet, pass an RPC URL via the --fork-url
flag.
forge test --fork-url <RPC_URL>
Forking is especially useful when you need to interact with existing contracts. You may choose to do integration testing this way, as if you were on an actual network.
Staging Testing
TODO
Fuzz Testing
function test_FuzzTestExample(
uint256 randomRequestId
) public raffleEntered {
vm.expectRevert("nonexistent request");
VRFCoordinatorV2Mock(vrfCoordinatorV2).fulfillRandomWords(
randomRequestId,
address(raffle)
);
}
Coverage
forge coverage
forge coverage --report debug
forge coverage --report debug > coverage-report.txt
Coverage line highlighting
forge coverage --report lcov
Open the command palette in VS Code (
CMD+SHIFT+P
orCTRL+SHIFT+P
by default) and type βDisplay Coverageβ
TODO: Exponential value display
Last updated