Blockchain

Hardhat Test | 테스트 중간에 Hardhat network를 reset 하기

Seo Mingyun 2023. 4. 13. 12:58

Hardhat에서는 Hardhat Network Helpers libaray를 제공하는데, 이를 이용하면 Hardhat에서 test code를 작성하고 실행할 때 네트워크 상황(블록 상황)을 제어할 수 있기 때문에 꽤 유용합니다.

 

컨트랙트 테스트를 실행할 때 유닛 별로는 각각 독립적으로 작동하지만 결국 블록체인 네트워크 상황 하에서 테스트가 진행되기 때문에 이전 테스트 유닛에서 네트워크의 state가 변경되고 새로운 블록이 생성된 경우에는 이전 테스트 때 변경된 상태가 다음 테스트 유닛에서도 영향을 줄 수 있습니다. 저는 이런 상황을 꽤 많이 직면했는데요. 항상 chai의 only 메서드를 이용해서 test를 진행하다가 이번에 Hardhat Network Helpers를 통해 실제 테스트 전에 네트워크를 한번씩 초기화해주는 방식으로 변경했습니다. 

 

Hardhat Network Helper 세팅

- module 설치

// npm 7+
npm install --save-dev @nomicfoundation/hardhat-network-helpers

// yarn
yarn add --dev @nomicfoundation/hardhat-network-helpers

 

- import 

// TypeScript
import { mine } from "@nomicfoundation/hardhat-network-helpers";

// JavaScript
const { mine } = require("@nomicfoundation/hardhat-network-helpers");

 

 

Hardhat Network Helpers의 Reset 

reset([url], [blockNumber])

 

reset 함수는 말 그대로 Hardhat network를 초기화 합니다. 입력한 JsonRPC URL과 Block Number를 기준으로 네트워크를 초기화해주며, Block Number가 입력되지 않으면 가장 최근 블록으로, 둘 다 입력되지 않으면 Hardhat local 네트워크로 초기화됩니다. 

 

- 사용 예시

beforeEach(async function () {
  // #1
  await network.provider.request({
    method: "hardhat_reset",
    params: [
      {
        forking: {
          jsonRpcUrl: <ALCHEMY_URL>,
          blockNumber: <BLOCK_NUMBER>,
        },
      },
    ],
  });
  
  // #2
  await reset(<ALCHEMY_URL>, <BLOCK_NUMBER>);
});

it("Test 1", async function () {
   ...
}

 

Hardhat Network Helpers의 loadFixture

loadFixture(<FUNC_NAME>)

 

loadFixture을 사용하면 원하는 상태(스냅샷)로 네트워크 상태를 설정할 수 있습니다. 

 

const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");
const { expect } = require("chai");

describe("Token contract", function () {
  async function deployTokenFixture() {
    const Token = await ethers.getContractFactory("Token");
    const [owner, addr1, addr2] = await ethers.getSigners();

    const hardhatToken = await Token.deploy();

    await hardhatToken.deployed();

    // Fixtures can return anything you consider useful for your tests
    return { Token, hardhatToken, owner, addr1, addr2 };
  }

  it("Should assign the total supply of tokens to the owner", async function () {
    const { hardhatToken, owner } = await loadFixture(deployTokenFixture);

    const ownerBalance = await hardhatToken.balanceOf(owner.address);
    expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
  });

  it("Should transfer tokens between accounts", async function () {
    const { hardhatToken, owner, addr1, addr2 } = await loadFixture(
      deployTokenFixture
    );

    // Test...
    
  });
});

 

위와 같이 특정 함수를 넘겨주면, 해당 함수가 수행된 상황에 대한 스냅샷을 찍습니다. 이후 동일한 함수로 loadFixture을 다시 호출하면 해당 함수를 수행하는 작업 대신에 이전에 찍은 스냅샷으로 네트워크를 복원합니다. 따라서 일일이 중복되는 컨트랙트 배포나 함수 실행을 할 필요가 없게 됩니다. 

 

다만, 주의해야 할 점은 anonymouns function과 함께 loadFixture을 사용하면 스냅샷을 사용하는 대신에 매번 함수가 새롭게 실행된다는 것입니다.

Correct usage: loadFixture(deployTokens)
Incorrect usage: loadFixture(async () => { ... })