Testing Solidity
Based on notes taken while migrating me3 contracts from Hardhat to Foundry
Foundry
A rewrite of #Dapptools using Rust: https://github.com/gakonst/foundry/
- use
forge test
- test naming is very important in the test contract:
- functions that begin with test are run as tests, eg
testThatSomethingIsTrue
- functions that begin with testFail expect some sort of revert and thus you should not check for failure in the test
- functions that begin with test are run as tests, eg
Different failure checks
Sample contract that will be used for testing:
contract Lol {
function betterBeOne (int x) external {
require(x == 1, "x needs to be 1");
}
}
==== try/catch with revert/require ====
Using a try/catch
, run the function that is expected to fail. If it does not enter the catch
it is because the function passed when it should not have. In this case, we force the test to fail by using revert()
. When the function enters the catch block, we are on the right path. We can then use a require
to validate any parts of the contract to make sure it was the correct failure.
This would be a good way to test many different require
statements in the called function.
function testIsNotOne () public {
try lol.betterBeOne(0) {
revert();
} catch Error(string memory error) {
require(
keccak256(abi.encodePacked(error)),
keccak256("x needs to be 1")
);
}
}
testFail
When you expect a test to fail but don't need to check the message, you can start your function name with testFail
and it will pass as long as a failure occurs
function testFailWhenNotOne () public {
.betterBeOne(0);
lol}
ds-test
Unit testing Solidity in Solidity, written by dapphub: https://github.com/dapphub/ds-test
- provides a number of assertions
- works with #Dapptools and #Foundry
- simplifies a lot of tests and provides logs via event emitting
contract TestLol is DSTest {
public lol;
Lol
function setUp () public {
= new Lol();
lol }
function testIsNotOne () public {
try lol.betterBeOne(0) {
();
fail} catch Error(string memory error) {
(error, "x needs to be 1");
assertEq}
}
}
Hardhat
- javascript testing framework
- easy to integrate when already working with javascript
- slower than other tools
- code from tests can be used directly in source code as you essentially have to do the same things (assuming no mocking)
- the layer of abstraction requires a lot more thought
Deploying third-party contracts
If you interact with existing contracts, there are two methods that you can use to test with them:
- fork the network the contracts exist on
- if they are open source, deploy the contracts into the test network
To deploy existing contracts on your network, you can create an importer contract. Here is an example using ENS contracts:
// ENSDeploy.sol
// SPDX-License-Identifier: MIT
import "@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol";
import "@ensdomains/ens-contracts/contracts/registry/FIFSRegistrar.sol";
import "@ensdomains/ens-contracts/contracts/registry/ReverseRegistrar.sol";
The contracts can be added to the project with:
npm i --save-dev @ensdomains/ens-contracts