Inline configuration for Solidity tests
Hardhat lets you override Solidity test settings using NatSpec comments, on a single test function or on a whole test contract. This is useful when different tests need different parameters. For example, a critical function may need more fuzz iterations, while an invariant test may need a greater depth.
Inline overrides take precedence over the global settings defined in your Solidity tests configuration. They can also be scoped to a Test Profile.
Syntax
Section titled “Syntax”Each override is a single line inside a NatSpec comment (line /// or block /** */), following the format hardhat-config: <key> = <value>.
Keys can be written in camelCase, snake_case, or kebab-case, and string values must be double-quoted:
/// hardhat-config: fuzz.runs = 10000/// hardhat-config: fuzz.maxTestRejects = 500/// hardhat-config: evmVersion = "cancun"function testTransferFuzz(uint256 amount) public { // ...}Block comments are also supported:
/** * hardhat-config: invariant.runs = 100 * hardhat-config: invariant.depth = 50 * hardhat-config: invariant.failOnRevert = true */function invariantBalanceAlwaysPositive() public { // ...}Overriding settings for a whole contract
Section titled “Overriding settings for a whole contract”A directive above a contract applies to every test it runs, including the ones it inherits:
/// hardhat-config: fuzz.runs = 500contract TransferTest is Test { function testTransferFuzz(uint256 amount) public { // ... }}If a test function sets the same setting, its value wins. The contract’s other settings still apply to it.
Scoping an override to a Test Profile
Section titled “Scoping an override to a Test Profile”An unprefixed directive applies under every Test Profile. To apply one under a single profile, prefix its key with the profile’s name. This works the same way above a function and above a contract:
/// hardhat-config: ci.fuzz.runs = 10000function testTransferFuzz(uint256 amount) public { // ...}Combine both forms in the same place to use different values. When its profile is selected, the prefixed directive takes precedence over the unprefixed one for the same key:
/// hardhat-config: fuzz.runs = 100/// hardhat-config: ci.fuzz.runs = 10000function testTransferFuzz(uint256 amount) public { // ...}This runs 10000 iterations under the ci profile, and 100 under any other.
Which directive applies
Section titled “Which directive applies”Directives are applied in this order of priority:
- Function-level over contract-level
- Profile-prefixed over unprefixed, within the same level
The level comes first, so a function’s unprefixed directive beats the contract’s prefixed one, even under that profile:
/// hardhat-config: ci.fuzz.runs = 10000contract TransferTest is Test { /// hardhat-config: fuzz.runs = 100 function testTransferFuzz(uint256 amount) public { // 100 runs under every profile, including `ci` }}Supported configuration keys
Section titled “Supported configuration keys”Keys with the fuzz./invariant. prefix apply only to that test type; the rest apply to the test as a whole and work on both fuzz and invariant tests.
| Key | Description |
|---|---|
fuzz.runs | Number of fuzz iterations to run |
fuzz.maxTestRejects | Maximum number of rejected inputs before aborting |
fuzz.showLogs | Whether to show console logs during fuzzing |
fuzz.timeout | Timeout for the fuzz test |
invariant.runs | Number of invariant test runs |
invariant.depth | Number of calls per run to attempt to break the invariant |
invariant.failOnRevert | Whether to fail the invariant if a revert occurs |
invariant.callOverride | Whether to override unsafe external calls |
invariant.timeout | Timeout for the invariant test |
allowInternalExpectRevert | See below |
isolate | Run each top-level call as a separate transaction in its own EVM context |
evmVersion | EVM version to use for this test |
allowInternalExpectRevert
Section titled “allowInternalExpectRevert”By default, the expectRevert cheatcode only catches reverts from external calls, that is, calls at a lower depth than the test itself. If your test directly calls an internal function that reverts, expectRevert won’t catch it and you’ll see an error like call didn't revert at a lower depth than cheatcode call depth.
Setting allowInternalExpectRevert to true allows expectRevert to work on calls at the same depth as the test:
/// hardhat-config: allowInternalExpectRevert = truefunction testInternalRevert() public { vm.expectRevert("some error"); myInternalHelper(); // reverts at the same call depth}Foundry compatibility
Section titled “Foundry compatibility”Hardhat also accepts the forge-config: prefix, so you can keep using it:
/// forge-config: fuzz.runs = 10000/// forge-config: fuzz.max-test-rejects = 500function testTransferFuzz(uint256 amount) public { // ...}The default. prefix is the one thing that changes. In Foundry it applies under every profile, because profiles inherit from default. In Hardhat it applies only under the default profile, so remove default to keep the Foundry behavior:
/// forge-config: default.fuzz.runs = 10000/// forge-config: fuzz.runs = 10000function testTransferFuzz(uint256 amount) public { // ...}