Testing
Forge is a command-line tool that ships with Foundry. Forge tests, builds, and deploys your smart contracts.
Test Contracts
Testing in Solidity is different from other frameworks like brownie.
Tests are written in in Solidity -> If the test function reverts, the test fails, otherwise it passes.
Essentially we are writing a separate contract containing test functions.
Test contracts end with
.t.sol
Usually, tests will be placed in
src/test
(or./test
in windows)
Test contracts are deployed to 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84
.
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
'sonlyOwner
modifier, then the test contract0xb4c...7e84
will have those permissions.
Assertions
Running Tests
Forge can run your tests with the
forge test
command.Forge will look for the tests anywhere in your source directory.
Any contract with a function that starts with
test
is considered to be a test.
Running a specific test function
forge test --match-contract ComplicatedContractTest --match-test testDeposit
run the tests in the test contract
ComplicatedContractTest
withtestDeposit
in the function name.
Implementation
pragma solidity ^0.8.13;
import "ds-test/test.sol";
import 'src/SimpleNameRegister.sol';
contract SimpleNameRegisterTest is DSTest {
//declare state var.
SimpleNameRegister simpleNameRegister;
address owner;
string test;
function setUp() public {
simpleNameRegister = new SimpleNameRegister();
owner = 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84; //test contract deployer
test = "what";
}
function testRegisterDepends() public {
simpleNameRegister.registerName(test);
bool success = (owner == simpleNameRegister.nameOwner(test));
assertTrue(success);
}
function testRelinquishDepends() public {
simpleNameRegister.relinquishName(test);
bool success = (simpleNameRegister.nameOwner(test) == address(0));
assertTrue(success);
}
import the contract to be tested (import 'src/SimpleNameRegister.sol';)
deploy it via factory pattern

function setUp()
The setup function is invoked before each test case is run:
serves to setup the necessary variables and conditions for your test functions to operate.
here we use it to deploy a fresh instance of SimpleNameRegister.sol before each test case is ran via
simpleNameRegister = new SimpleNameRegister()
To show that there is no spillover:
create storage string variable test and assign it "what"
The first function testRegisterDepends() registers ownership of name "what".
Then we call testRelinquishDepends() to check if we can relinquish ownership.

testRelinquishDepends() fails, indicating that the effects from testRegisterDepends() do not spill into testRelinquishDepends().
Since setUp() is invoked before each test case -> each test function interacts with a seperate instance of SimpleNameRegister.sol
Cheatcodes
Reference: https://book.getfoundry.sh/cheatcodes/
manipulate the state of the blockchain -> change block number
change your identity
test for specific reverts and events.
Cheatcodes are functions on contract at address: 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D
To execute a cheatcode function, we need to interface with this contract from our test contract.
Therefore in our test contract we need to do 3 things:
import interface Cheatcodes{} (or just copypasta the interface above your contract like below)
declare interface object as state variable
assignment of state variable: pass the cheatcode contract address into the interface object

Implementing Cheatcode: Prank
prank
: changes the next call's msg.sender to the input address.This allows us to modify msg.sender in between function calls.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "ds-test/test.sol";
import 'src/SimpleNameRegister.sol';
interface CheatCodes {
function prank(address) external;
function expectRevert(bytes calldata) external;
function startPrank(address) external;
function stopPrank() external;
}
contract SimpleNameRegisterTest is DSTest {
// declare state var.
SimpleNameRegister simpleNameRegister;
CheatCodes cheats;
address adversary;
function setUp() public {
simpleNameRegister = new SimpleNameRegister();
cheats = CheatCodes(HEVM_ADDRESS);
adversary = 0xE6A2e85916802210147e366D4431f5ca4dD51a78;
}
// user can register an available name
function testRegisterName(string memory _testString) public {
simpleNameRegister.registerName(_testString);
bool success = (address(this) == simpleNameRegister.nameOwner(_testString));
assertTrue(success);
}
// user can register an available name and relinquish it
function testRelinquishName(string memory _testString) public {
simpleNameRegister.registerName(_testString);
simpleNameRegister.relinquishName(_testString);
bool success = (simpleNameRegister.nameOwner(_testString) == address(0));
assertTrue(success);
}
// user cannot relinquish a name that does not belong to them
function testRelinquishAsNotOwner(string memory _testString) public {
simpleNameRegister.registerName(_testString);
cheats.startPrank(adversary);
cheats.expectRevert(bytes("The provided name does not belong to you!"));
simpleNameRegister.relinquishName(_testString);
cheats.stopPrank();
}
// user cannot register a name that already has an owner
function testRegisterUnavailableName(string memory _testString) public {
simpleNameRegister.registerName(_testString);
cheats.startPrank(adversary);
cheats.expectRevert(bytes("The provided name has already been registered!"));
simpleNameRegister.registerName(_testString);
cheats.stopPrank();
}
}
startPrank

Sets msg.sender
for all subsequent calls until stopPrank
is called.
stopPrank

Stops startPrank
, resetting msg.sender
and tx.origin
to the values before startPrank
was called.
expectRevert
We can do so using expectRevert(bytes calldata)
; it will expect a revert to occur on the next call.
insert it before making a function call that is expected to fail/revert
if the next call does not revert, then
expectRevert
will.After calling
expectRevert
, calls to other cheatcodes before the reverting call are ignored.
For require statements, provide the error string as defined in the require:
cheats.expectRevert(bytes("The provided name does not belong to you!"));
Last updated