## Testing

Forge runs tests written in Solidity. Test files live in `test/` and test functions are prefixed with `test`.

:::terminal

```bash
// [!include ~/snippets/output/hello_foundry/forge-test:command]
```

```ansi
// [!include ~/snippets/output/hello_foundry/forge-test:output]
```

:::

### Writing tests

Create a test contract that inherits from `Test`:

```solidity [test/Counter.t.sol]
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

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

contract CounterTest is Test {
    Counter counter;

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

    function test_Increment() public {
        counter.increment();
        assertEq(counter.number(), 1);
    }

    function test_SetNumber() public {
        counter.setNumber(42);
        assertEq(counter.number(), 42);
    }
}
```

Key conventions:

* Test files end with `.t.sol`
* Test contracts inherit from `forge-std/Test.sol`
* Test functions start with `test_` or `test`
* `setUp()` runs before each test

### Traces

Traces show a tree of all calls made during a test, helping you understand execution flow and debug failures.

#### Stack traces

When a test fails, use `-vvv` to see a stack trace showing exactly where the revert occurred. This is the most common way to debug test failures.

:::terminal

```bash
$ forge test -vvv
```

```ansi
// [!include ~/snippets/output/cheatcodes/forge-test-fail-vvv:output]
```

:::

The trace shows the call hierarchy with the revert bubbling up, and the **Backtrace** pinpoints the exact location in your code.

#### Full traces

Use `-vvvv` to see traces for all tests, including passing ones. This helps you understand execution flow, verify call order, and check gas usage for individual operations.

:::terminal

```bash
$ forge test -vvvv
```

```ansi
// [!include ~/snippets/output/cheatcodes/forge-test-vvvv:output]
```

:::

#### Reading traces

* **Gas costs** appear in brackets: `[29808]`
* **Contract and function names** are color-coded
* **Call types** are annotated: `[staticcall]` for view/pure functions
* **Return values** show what each call returned: `← [Return] 0` for a value, `← [Stop]` for void
* **Indentation** shows the call hierarchy—nested calls are indented under their parent

### Verbosity levels

Control how much detail Forge outputs with `-v` flags:

| Flag | Shows |
|------|-------|
| (none) | Pass/fail summary only |
| `-v` | Test names |
| `-vv` | Logs emitted during tests |
| `-vvv` | Traces for failing tests |
| `-vvvv` | Traces for all tests, including setup |
| `-vvvvv` | Traces with storage changes |

Use `-vvv` for debugging failures, `-vvvv` when you need to see successful test execution, and `-vvvvv` when tracking state changes.

### Filtering tests

Run specific tests:

By name:

:::terminal

```bash
// [!include ~/snippets/output/test_filters/forge-test-match-test:command]
```

```ansi
// [!include ~/snippets/output/test_filters/forge-test-match-test:output]
```

:::

By contract:

:::terminal

```bash
// [!include ~/snippets/output/test_filters/forge-test-match-contract:command]
```

```ansi
// [!include ~/snippets/output/test_filters/forge-test-match-contract:output]
```

:::

By path:

:::terminal

```bash
// [!include ~/snippets/output/test_filters/forge-test-match-path:command]
```

```ansi
// [!include ~/snippets/output/test_filters/forge-test-match-path:output]
```

:::

Combine filters:

:::terminal

```bash
// [!include ~/snippets/output/test_filters/forge-test-match-contract-and-test:command]
```

```ansi
// [!include ~/snippets/output/test_filters/forge-test-match-contract-and-test:output]
```

:::

Exclude tests with `--no-match-*` variants:

```bash
$ forge test --no-match-test test_Skip
```

### Fuzz testing

Forge automatically fuzzes test functions that take parameters:

```solidity
function testFuzz_SetNumber(uint256 x) public {
    counter.setNumber(x);
    assertEq(counter.number(), x);
}
```

Forge generates random inputs and runs the test multiple times (256 by default):

:::terminal

```bash
// [!include ~/snippets/output/fuzz_testing/forge-test-success-fuzz:command]
```

```ansi
// [!include ~/snippets/output/fuzz_testing/forge-test-success-fuzz:output]
```

:::

Configure fuzzing:

```toml [foundry.toml]
[fuzz]
runs = 1000
max_test_rejects = 65536
seed = "0x1234"
```

Constrain inputs with `vm.assume(){:solidity}`:

```solidity
function testFuzz_Transfer(uint256 amount) public {
    vm.assume(amount > 0 && amount <= 1000 ether);
    // Test with constrained amount
}
```

Or use `bound(){:solidity}` to clamp values:

```solidity
function testFuzz_Transfer(uint256 amount) public {
    amount = bound(amount, 1, 1000 ether);
    // Test with bounded amount
}
```

### Table testing

Foundry v1.3.0 comes with support for table testing, which enables the definition of a dataset (the "table") and the execution of a test function for each entry in that dataset. This approach helps ensure that certain combinations of inputs and conditions are tested.

In forge, table tests are functions named with `table` prefix that accepts datasets as one or multiple arguments:

```solidity
function tableSumsTest(TestCase memory sums) public
```

```solidity
function tableSumsTest(TestCase memory sums, bool enable) public
```

The datasets are defined as forge fixtures which can be:

* storage arrays prefixed with `fixture` prefix and followed by dataset name
* functions named with `fixture` prefix, followed by dataset name. Function should return an (fixed size or dynamic) array of values.

#### Single dataset

In following example, `tableSumsTest` test will be executed twice, with inputs from `fixtureSums` dataset: once with `TestCase(1, 2, 3)` and once with `TestCase(4, 5, 9)`.

```solidity
struct TestCase {
    uint256 a;
    uint256 b;
    uint256 expected;
}

function fixtureSums() public returns (TestCase[] memory) {
    TestCase[] memory entries = new TestCase[](2);
    entries[0] = TestCase(1, 2, 3);
    entries[1] = TestCase(4, 5, 9);
    return entries;
}

function tableSumsTest(TestCase memory sums) public pure {
    require(sums.a + sums.b == sums.expected, "wrong sum");
}
```

It is required to name the `tableSumsTest`'s `TestCase` parameter `sums` as the parameter name is resolved against the available fixtures (`fixtureSums`). In this example, if the parameter is not named `sums` the following error is raised: `[FAIL: Table test should have fixtures defined]`.

#### Multiple datasets

`tableSwapTest` test will be executed twice, by using values at the same position from `fixtureWallet` and `fixtureSwap` datasets.

```solidity
struct Wallet {
    address owner;
    uint256 amount;
}

struct Swap {
    bool swap;
    uint256 amount;
}

Wallet[] public fixtureWallet;
Swap[] public fixtureSwap;

function setUp() public {
    // first table test input
    fixtureWallet.push(Wallet(address(11), 11));
    fixtureSwap.push(Swap(true, 11));

    // second table test input
    fixtureWallet.push(Wallet(address(12), 12));
    fixtureSwap.push(Swap(false, 12));
}

function tableSwapTest(Wallet memory wallet, Swap memory swap) public pure {
    require(
        (wallet.owner == address(11) && swap.swap) || (wallet.owner == address(12) && !swap.swap), "not allowed"
    );
}
```

The same naming requirement mentioned above is relevant here.

### Mutation testing

Mutation testing checks the strength of your test suite by making small changes, or mutants, to your source code and re-running your tests. A mutant is killed when at least one test fails. A mutant survives when the changed code still passes the selected tests.

:::info
Mutation testing is currently an MVP. It is ready for early use and feedback, but the workflow, reporting, and supported project configurations are still expected to evolve.
:::

Run mutation testing with `forge test --mutate`:

```bash
$ forge test --mutate
```

Forge first runs the selected tests as a baseline. Mutation testing only starts if the baseline has at least one passing test and no failing tests.

#### Selecting files

Pass paths to mutate only those files:

```bash
$ forge test --mutate src/Vault.sol src/Token.sol
```

Use `--mutate-path` to select files with a glob pattern:

```bash
$ forge test --mutate --mutate-path 'src/**/*.sol'
```

Use `--mutate-contract` to select contracts by name:

```bash
$ forge test --mutate --mutate-contract 'Vault|Token'
```

`--mutate-path` and `--mutate-contract` cannot be combined. `--mutate-path` also cannot be combined with explicit paths passed to `--mutate`.

#### Selecting tests

Regular test filters still select the baseline tests and the tests run against each mutant:

```bash
$ forge test --mutate src/Vault.sol --match-contract VaultTest
```

This lets you scope a mutation run to the tests that should detect changes in a specific contract.

#### Parallel workers

Forge runs mutants in parallel. By default, it uses the number of logical CPU cores.

Set the worker count with `--mutation-jobs`:

```bash
$ forge test --mutate src/Vault.sol --mutation-jobs 4
```

Passing `0` also uses the number of logical CPU cores:

```bash
$ forge test --mutate src/Vault.sol --mutation-jobs 0
```

Parallel mutation testing uses isolated temporary workspaces per mutant. Dependency directories such as `lib`, `node_modules`, and `dependencies` are symlinked into those workspaces for performance.

#### Timeouts

Use `--mutation-timeout` to set a best-effort wall-clock timeout, in seconds, for each mutant:

```bash
$ forge test --mutate src/Vault.sol --mutation-timeout 30
```

Timed-out mutants are reported separately from killed, survived, skipped, and invalid mutants.

You can also configure the timeout in `foundry.toml`:

```toml [foundry.toml]
[mutation]
timeout = 30
```

#### Operators

Mutation testing supports these operator groups:

* `assembly`
* `assignment`
* `binary-op`
* `delete-expression`
* `elim-delegate`
* `require`
* `unary-op`

All operator groups are enabled by default. Exclude specific operators in `foundry.toml`:

```toml [foundry.toml]
[mutation]
exclude_operators = ["assembly", "elim-delegate"]
```

Use `include_operators` to re-enable operators that are excluded by default:

```toml [foundry.toml]
[mutation]
include_operators = ["assembly"]
```

#### Reports

The report includes counts for:

* **Survived**: mutants that passed the selected tests
* **Killed**: mutants that caused a test failure
* **Invalid**: mutants that could not be compiled or run
* **Skipped**: redundant mutants on a span or expression after another mutant in that span survived
* **Timed out**: mutants that exceeded `mutation.timeout` or `--mutation-timeout`

Skipped and invalid counts can vary with `--mutation-jobs`, because higher parallelism can start more mutants before a survivor is known.

The mutation score is:

```text
killed / (killed + survived)
```

Focus on survived mutants first. Each survived mutant points to the source location and mutation that your tests did not catch.

Survived mutants do not currently make `forge test --mutate` fail, and there is no threshold flag yet. To gate mutation testing in CI, run with `--json` and enforce your own threshold from the JSON output.

```bash
$ forge test --mutate --json
```

The JSON output has this shape:

```json
{
  "summary": {
    "total": 12,
    "killed": 8,
    "survived": 2,
    "invalid": 1,
    "skipped": 1,
    "timed_out": 0,
    "mutation_score": 80.0,
    "duration_secs": 12.34
  },
  "survived_mutants": {
    "src/Vault.sol": [
      {
        "line": 42,
        "column": 17,
        "original": ">",
        "mutant": ">="
      }
    ]
  }
}
```

#### Limitations

Mutation testing cannot be combined with `--list`, `--debug`, `--flamegraph`, `--flamechart`, `--junit`, `--dump`, `--showmap`, or `--showmap-out`.

Mutation testing also rejects projects with `ffi = true`, write-capable file-system permissions that can reach symlinked dependency directories, or inline per-test network overrides.

### Symbolic testing

Symbolic testing explores your code with symbolic inputs instead of concrete ones, searching feasible execution paths within the current symbolic EVM model and configured bounds for a counterexample that violates a property. When Forge reports a failure, it first replays the concrete input or invariant sequence through the normal executor, so the failure is backed by a concrete example.

:::info
Symbolic testing is currently an MVP. It is ready for early use and feedback, but the modeled EVM surface, configuration, and reporting are still expected to evolve.
:::

Symbolic tests are Solidity functions named `check*` or `prove*`. They are only discovered when symbolic mode is enabled with `--symbolic`:

```solidity
contract MathSymbolicTest is Test {
    function check_average(uint256 a, uint256 b) external pure {
        uint256 average;
        unchecked {
            average = (a + b) / 2;
        }

        // Forge should find an overflow counterexample.
        assertGe(average, a <= b ? a : b);
    }
}
```

Run it with:

```bash
$ forge test --symbolic --match-test check_average
```

Symbolic testing requires an SMT solver to be installed. The default solver is `z3`:

```bash
$ brew install z3        # macOS
$ sudo apt-get install z3 # Ubuntu
```

#### Writing symbolic tests

Function parameters become symbolic inputs derived from the ABI, and the executor explores the feasible paths:

* `require(...)` and `vm.assume(...)` prune paths when their condition is false.
* `assert`, forge-std assertions, and DSTest failure signals are treated as properties to disprove.
* User reverts terminate the current path.

When `--symbolic` is enabled, `invariant*` and `statefulFuzz*` functions are explored as bounded symbolic call sequences instead of using the normal fuzzer.

#### Results

Forge reports symbolic outcomes as:

* **`PASS`**: every explored path finished without a feasible failure under the currently modeled semantics and configured bounds.
* **`FAIL`**: the solver found a failing input or invariant sequence, and Forge replayed it concretely before reporting it.
* **`FAIL: incomplete symbolic execution (...)` / `Incomplete`**: Forge could not complete the search or validate a counterexample. Treat this as "not established", not as a proof.

A `PASS` is scoped to the current symbolic model and configured bounds; it does not cover skipped dynamic lengths, deeper invariant sequences, larger loop bounds, unmodeled behavior, arbitrary unknown external code, or cryptographic preimage/collision properties.

#### Configuration

Tune the exploration bounds and solver in `foundry.toml`:

```toml [foundry.toml]
[profile.default.symbolic]
solver = "z3"
timeout = 30
max_depth = 10000
max_paths = 1024
max_solver_queries = 10000
```

Symbolic exploration is bounded by configuration, including `symbolic.max_depth`, `symbolic.max_paths`, `symbolic.max_solver_queries`, dynamic calldata length settings, and `symbolic.invariant_depth`.

Bounds can also be set per test with inline `forge-config` annotations:

```solidity
/// forge-config: default.symbolic.invariant_depth = 4
function invariant_counterNeverFive() public view {
    assertTrue(counter.value() != 5);
}
```

#### Limitations

The symbolic engine is not a complete revm-equivalent EVM model. Unsupported constructs report `incomplete` rather than a proof, and some supported semantics are bounded or approximate. Notable gaps include gas accounting, Cancun+ `SELFDESTRUCT`, arbitrary unknown external code, and cryptographic preimage or collision properties. The exact unsupported-feature reason is preserved in the test output.

### Testing reverts

Use `vm.expectRevert(){:solidity}` to test that a call reverts:

```solidity
function test_RevertWhen_Unauthorized() public {
    vm.expectRevert("Not authorized");
    restricted.doSomething();
}
```

Match a custom error:

```solidity
function test_RevertWhen_InsufficientBalance() public {
    vm.expectRevert(Token.InsufficientBalance.selector);
    token.transfer(address(0), 1000);
}
```

:::terminal

```bash
// [!include ~/snippets/output/cheatcodes/forge-test-cheatcodes-expectrevert:command]
```

```ansi
// [!include ~/snippets/output/cheatcodes/forge-test-cheatcodes-expectrevert:output]
```

:::

### Testing events

Use `vm.expectEmit(){:solidity}` to verify events are emitted:

```solidity
function test_EmitsTransfer() public {
    vm.expectEmit(true, true, false, true);
    emit Transfer(alice, bob, 100);
    
    token.transfer(bob, 100);
}
```

The four booleans specify which topics and data to check.

### Forking

Test against live chain state:

```bash
$ forge test --fork-url https://ethereum.reth.rs/rpc
```

Or configure in `foundry.toml`:

```toml [foundry.toml]
[profile.default]
eth_rpc_url = "https://ethereum.reth.rs/rpc"
```

Pin to a specific block for reproducible tests:

```bash
$ forge test --fork-url https://ethereum.reth.rs/rpc --fork-block-number 18000000
```

### Cheatcodes

Forge provides cheatcodes via the `vm` object to manipulate the test environment:

```solidity
// Set block timestamp
vm.warp(1700000000);

// Set block number
vm.roll(18000000);

// Impersonate an address
vm.prank(alice);
contract.doSomething();

// Give ETH to an address
vm.deal(alice, 100 ether);

// Modify storage
vm.store(address(token), bytes32(0), bytes32(uint256(1000)));
```

See the [cheatcodes reference](/reference/cheatcodes/overview) for the full list.

### Watch mode

Re-run tests when files change:

```bash
$ forge test --watch
```
