Forge is a fast and flexible testing framework, inspired by Dapp. Part of the Foundry toolkit, a blazing fast, portable and modular kit for Ethereum development written in Rust.
As foundry is evolving fast and has a lot of contributors, the recommended way to install is using foundryup tool.
On Linux and macOS systems can be installed like:
$ curl -L https://foundry.paradigm.xyz | bash
This command downloads foundryup. Then you can install foundry by running:
$ foundryup
In Windows, you need to install it by building from the source.
First, you need to install rust, you can follow the official documentation here:
Then you can use cargo (the Rust package manager) to install foundry:
$ cargo install --git https://github.com/gakonst/foundry --bins --locked
As foundry is evolving fast with a lot of contributors it’s recommended to use forgeup for managing various versions of Forge.
Setup the folder that you want to use and initialize forge:
$ mkdir my-app
$ cd my-app
$ forge init
This creates a simple project structure, you can of course configure it according to your needs, but for simplicity in this tutorial, I will use the default structure.
The libraries that you install will go here, by default, ds-test is installed, this one allows you to run tests, create asserts or logs. If you want to install other libraries you can run:
$ forge install openzeppelin/openzeppelin-contracts
This will add the openZeppelin repo to lib/openzeppelin-contracts and you can use it with a simple import:
import "openzeppelin-contracts/contracts/access/Ownable.sol";
The path for the libs can be configured with remappings. See Appendix B — Using Remappings for further information.
The src folder is where your contracts are. We are going to use this simple example to run a few tests:
pragma solidity 0.8.10;
contract Blacksmith {
address public owner;
constructor(address _owner){
owner = _owner;
}
function onlyOwner() public{
require(msg.sender == owner, "Not the owner");
}
}
This is where your test lives, as a best practice, the test file is named exactly as the main contract with an appended “.t”, if your contract is named blacksmith.sol the test should be named blacksmith.t.sol.
We can define where the src and test folders are using environment variables. See Appendix A — Additional Resources for further information.
What makes a .sol file a test is inheriting from the DSTest library, which provides a complete list of assertions, equality checks, logs, and helpers that you can use. See the complete list on:
In order to run a test with forge you only need to use the command forge test
. Use the following code and try it out:
pragma solidity 0.8.10;
import "ds-test/test.sol";
import "../Blacksmith.sol";
contract User {
function callOnlyOwner(Blacksmith b) public {
b.onlyOwner();
}
}
contract BlacksmithTest is DSTest {
Blacksmith blacksmith;
User user;
function setUp() public {
blacksmith = new Blacksmith(address(this));
user = new User();
}
function testOwner() public {
assertEq(blacksmith.owner(),address(this));
}
function testFailOwner() public {
user.callOnlyOwner(blacksmith);
}
function testWrongOwner() public {
user.callOnlyOwner(blacksmith); // This test will fail
}
}
Forge and Dapp use certain keywords in functions to run tests: setUp, test, and testFail.
setUp()
This function is called before each test or testFail function, perfect for initialization of contracts and additional setup.
test*()
Functions that start with the keyword test will get run with forge and expect to not revert in order to pass.
testFail*()
Functions that start with the keyword test will get run with forge and expect to revert in order to pass.
Forge implements some cheat codes that make failing tests in a better way, we will cover the cheat codes in another tutorial.
There is so much to cover with forge, but want to keep these tutorials short, will keep a small list of resources and tutorials updated in Appendix A — Additional Resources. Feel free to share this article or contact me by sending a DM on Twitter @Crisgarner.
Remappings help when you want to use NPM style imports, or have a hardhat repo that installs their packages in node_modules
, for example:
import "@openzeppelin/contracts/access/Ownable.sol";
In order for Forge to know where to find this dependency you need to create a remappings.txt
file at the top level of your project directory. So the steps you need to follow are:
forge install openzeppelin/openzeppelin-contracts
(this will add the repo to lib/openzepplin-contracts
)touch remappings.txt
remappings.txt
@openzeppelin/=lib/openzeppelin-contracts/