Skip to content

Add command to scaffold test files #5466

@0x-r4bbit

Description

@0x-r4bbit

Component

Forge

Describe the feature you would like

This came up in the support chat and I think it's a reasonable feature to support:

Is there a forge command to create test files? If not does forge add test seem like a valuable addition?

Creating test file typically involves repeated steps:

  1. Creating the test file
  2. Adding the necessary imports
  3. Adding the setup code for the test contract

^ This is the most minimal case, but depending on how tests should be structure, this can take different forms. Here are some ideas on what this could look like:

Creating a simple test file

For example, if I have a contract Counter that lives in src/Counter.sol and say I run

$ forge create-test Counter

Then this would create test/Counter.t.sol with the following content:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../src/Counter.sol";

contract CounterTest is Test {
    Counter public counter;

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

So in a "convention over configuration" fashion, this would derive the test contracts name and file name based on the to-be-tested contract.

Appendix: Scaffold test functions for methods as well

We could probably even be smart, look into the existing contract, figure out all functions that need test and add dedicated (failing) test* functions as well.

So given a source that looks like:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract Counter {
    uint256 public number;

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }
}

We can figure out that setNumber() and increment() need to be tested, so when generating a test file, the contents would look like this:

pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../src/Counter.sol";

contract CounterTest is Test {
    Counter public counter;

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

   function test_setNumber() public {
     assert(false, "NOT IMPLEMENTED")
   }

   function test_increment() public {
     assert(false, "NOT IMPLEMENTED")
   }
}

If we go down that route, we might even take it yet a step further.

Support different scaffold types

There a various styles and best practices when writing tests in foundry. While the draft above sets up simple test files that get you going, they probably don't scale very well when the contracts to be tested are big.

One of the practices one can see in bigger projects is that every contract function to be tested gets its own test contract so it can be used as "describe()" block.

For example, the Counter example above, could then be written as (ignore the fact that this is overkill for this kind of contract):

pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../src/Counter.sol";

contract Counter_Test is Test {
    Counter public counter;

    function setUp() public virtual {
        counter = new Counter();
    }
}

contract SetNumber_Test is Counter_Test {
  function setUp() public override {
    Counter_Test.setup();
  }

  function test_SetNumber() public {
    // write assertions
  }
}

contract Increment_Test is Counter_Test {
  function setUp() public override {
    Counter_Test.setup();
  }

  function test_Increment() public {
    // write assertions
  }
}

Something like that could then be generated using something like

$ forge create-test Counter --scaffold-type describe # or something else

Consider introducing test type commands

As a matter of fact, given that there's different types of tests as well we might even want to introduce test type commands altogether:

$ forge create-unit-test Counter

or

$ forge create-fuzz-test Counter

Make this part of an over all contract scaffold command

At this point, we might as well think about introducing a dedicated commands to scaffold entire contracts, including their test counterparts. So for example:

$ forge create-contract Counter

Would create src/Counter.sol with the contents:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract Counter {

  constructor() {

  }
}

It would also create src/test/Counter.t.sol and script/Counter.s.sol respectively.
Foundry could then have some default test style config options in foundry.toml that are used when generating those source files.

Additional context

Most of the above is something one can typically find in CLI tools for other platforms and frameworks as well.
For example angular-cli and vue-cli provide commands to easily spin up components including their tests.

I think we can draw some inspiration from there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions