NomicFoundation / hardhat

Hardhat is a development environment to compile, deploy, test, and debug your Ethereum software.
https://hardhat.org
Other
7k stars 1.36k forks source link

Resetting the snapshot in `afterEach` does not work when using `loadFixture` #5395

Closed hexcowboy closed 1 week ago

hexcowboy commented 3 weeks ago

Version of Hardhat

2.22.5

What happened?

Resetting the snapshot in afterEach does not work when using loadFixture.

Minimal reproduction steps

import { expect } from "chai";
import hre from "hardhat";
import { parseGwei } from "viem";

import {
  type SnapshotRestorer,
  loadFixture,
  takeSnapshot,
  time,
} from "@nomicfoundation/hardhat-toolbox-viem/network-helpers";

describe("Block Number Test", function () {
  let snapshot: SnapshotRestorer;

  before(async function () {
    snapshot = await takeSnapshot();
  });

  afterEach(async function () {
    await snapshot.restore();
  });

  const deploy = async () => {
    const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;

    const lockedAmount = parseGwei("1");
    const unlockTime = BigInt((await time.latest()) + ONE_YEAR_IN_SECS);

    const lock = await hre.viem.deployContract("Lock", [unlockTime], {
      value: lockedAmount,
    });

    return { lock };
  };

  it("should have block number 0 initially", async function () {
    const { lock } = await loadFixture(deploy);
    const client = await hre.viem.getPublicClient();
    const block = await client.getBlock();
    expect(block.number).to.equal(0n);

    const testClient = await hre.viem.getTestClient();
    await testClient.mine({ blocks: 20 });
  });

  it("should have block number 0 in a new test", async function () {
    const { lock } = await loadFixture(deploy);
    const client = await hre.viem.getPublicClient();
    const block = await client.getBlock();
    expect(block.number).to.equal(0n);
  });
});
$ npx hardhat test
FixtureSnapshotError: There was an error reverting the snapshot of the fixture.

This might be caused by using nested loadFixture calls in a test, for example by using multiple beforeEach calls. This is not supported yet.

Search terms

snapshot beforeEach afterEach loadFixture FixtureSnapshotError

ChristopherDedominici commented 1 week ago

Hi @hexcowboy, thanks for reporting the issue. The behavior you're experiencing is a current limitation that we have.
snapshot.restore cannot be used when loadFixture is used, because loadFixture already resets the snapshot. I opened this issue to further investigate on a better approach that we could use.

In the meantime, you can use this code as a workaround:

import { expect } from "chai";
import hre from "hardhat";
import { parseGwei } from "viem";

import {
  type SnapshotRestorer,
  loadFixture,
  takeSnapshot,
  time,
} from "@nomicfoundation/hardhat-toolbox-viem/network-helpers";

describe("Block Number Test", function () {
  let snapshot: SnapshotRestorer;

  before(async function () {
    snapshot = await takeSnapshot();
  });

  const deploy = async () => {
    const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;

    const lockedAmount = parseGwei("1");
    const unlockTime = BigInt((await time.latest()) + ONE_YEAR_IN_SECS);

    const lock = await hre.viem.deployContract("Lock", [unlockTime], {
      value: lockedAmount,
    });

    return { lock };
  };

  it("should have block number 1 initially because loadFixture is called and it mines 1 block", async function () {
    const { lock } = await loadFixture(deploy);
    const client = await hre.viem.getPublicClient();
    const block = await client.getBlock();

    // Here the expected value is 1, not 0. Because 'deploy' is passed to 'loadFixtureFixture' and
    // when executed, it mines 1 block
    expect(block.number).to.equal(1n);

    const testClient = await hre.viem.getTestClient();
    await testClient.mine({ blocks: 20 });
  });

  describe("here you can reset the snapshot", function () {
    before(async function () {
      // Here we can restore the snapshot because 'loadFixtureFixture' is not used in the next 'it' block
      await snapshot.restore();
    });

    it("should have block number 0 in a new test because the snapshot is restored", async function () {
      const client = await hre.viem.getPublicClient();
      const block = await client.getBlock();
      expect(block.number).to.equal(0n);
    });
  });
});

I'll close this issue because the solution I provided should work for you. But if not, please feel free to reopen it

hexcowboy commented 1 week ago

thanks @ChristopherDedominici

i'm still confused on how i can work around this limitation for tests that require both a snapshot.restore and a loadFixture for all it blocks. in your example the last test can't use a loadFixture in the next it block, but what if my tests require it to function properly? or is that currently not possible?