[Try the Hardhat 3 alpha release]

#Writing unit tests in Solidity

Hardhat supports writing tests in both TypeScript and Solidity. TypeScript is typically used for higher-level integration tests, whereas Solidity is better suited for unit tests. This guide explains how to add Solidity tests to a Hardhat project, run them, and configure how they are executed. It assumes familiarity with Solidity tests and isn't meant to serve as an introduction to them.

# Writing Solidity tests

A Solidity file is considered a test file if it's inside the test/ directory, or if it's inside the contracts/ directory and ends with .t.sol. Both of these directories can be changed in your Hardhat configuration, but these are the defaults.

If a contract in a test file has at least a function that starts with test, it's considered a test contract. When the tests are run, Hardhat deploys every test contract and calls each of its test functions.

For example, if you have a contracts/CounterTest.t.sol or test/CounterTest.sol file with the following contract:

contract CounterTest {
    function testInc() public {
        Counter counter = new Counter();
        counter.inc();
        require(counter.count() == 1, "count should be 1");
    }
}

the test runner deploys the CounterTest contract and calls its testInc function. If the function execution reverts, the test is considered failed.

Hardhat also supports fuzz tests, which are similar to regular tests but accept parameters. When the tests are executed, fuzz test functions are called multiple times with random values as arguments:

contract CounterTest {
    function testIncBy(uint by) public {
        Counter counter = new Counter();
        counter.incBy(by);
        require(counter.count() == by, "count should match the 'by' value");
    }
}

#Assertion libraries

In the previous example, the error message doesn't show the actual value of by that made the test fail. That's because interpolating the value into the string isn't straightforward in Solidity. To get better error messages, plus other useful functionality, you can use an assertion library like forge-std.

To use forge-std in a Hardhat project, first install it:

npm
pnpm
npm install --save-dev github:foundry-rs/forge-std#v1.9.7
pnpm install --save-dev github:foundry-rs/forge-std#v1.9.7

You can then import the Test base contract and extend your test contract from it. This lets you use helper functions like assertEq, which shows the mismatched values when the assertion fails:

import { Test } from "forge-std/src/Test.sol";

contract CounterTest is Test {
    function testIncBy(uint by) public {
        Counter counter = new Counter();
        counter.incBy(by);
        assertEq(counter.count(), by, "count should match the 'by' value");
    }
}

#Setup functions

Both the unit and fuzz test examples shown above create an instance of the Counter contract. You can share setup logic across tests using the setup function, which is called before each test execution:

contract CounterTest {
    Counter counter;

    function setup() public {
      counter = new Counter();
    }

    function testInc() public {
        counter.inc();
        require(counter.count() == 1, "count should be 1");
    }

    function testIncBy(uint by) public {
        counter.incBy(by);
        require(counter.count() == by, "count should match the 'by' value");
    }
}

# Running Solidity tests

You can run all the tests in your hardhat project using the test task:

npm
pnpm
npx hardhat test
pnpm hardhat test

If you only want to run your Solidity tests, use the test solidity task instead:

npm
pnpm
npx hardhat test solidity
pnpm hardhat test solidity

You can also pass one or more paths as arguments to these tasks, in which case only those files are executed:

npm
pnpm
npx hardhat test <test-file-1> <test-file-2> ...
npx hardhat test <test-file-1> <test-file-2> ...

# Configuring Solidity tests

You can configure how Solidity tests are executed in your Hardhat configuration.

#Configuring the tests location

By default, Hardhat treats every Solidity file in the test/ directory as a test file. To use a different location, set the paths.tests.solidity field:

paths: {
  tests: {
    solidity: "./solidity-tests"
  }
},

#Configuring the tests execution

To configure how Solidity tests are executed, use the solidityTest property in the Hardhat configuration.

For example, the ffi cheatcode is disabled by default for security reasons, but you can enable it:

solidityTest: {
  ffi: true
},

It's also possible to modify the execution environment of the tests. For example, you can modify the address that is returned by msg.sender:

solidityTest: {
  sender: "0x1234567890123456789012345678901234567890"
},