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.
Test
Description
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";
Example Test Contract
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))
);
}
}
Tests are deployed to 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84. If you deploy a contract within your test, then 0xb4c...7e84 will be its deployer. If the contract deployed within a test gives special permissions to its deployer, such as Ownable.sol's onlyOwner modifier, then the test contract 0xb4c...7e84 will have those permissions.
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)
);
}