コントラクトのテストコードを書く

HardhatでNFTのテストコードを作成しようHardhatでNFTのテストコードを作成しよう

前回はテスト対象のスマートコントラクトを書きました。

今回はいよいよテスト対象のコントラクトをテストするコードを書きます。

テストのディレクトリを作成する

まずはスマートコントラクト用のディレクトリを作成しましょう。

VSCodeやターミナルからtestディレクトリを作ってください。

.
├── contracts
├── test  // ★ここを追加
├── hardhat.config.js
├── node_modules
├── package-lock.json
└── package.json

テストコードの雛形を作成する

いよいよテストコードを作っていきます。

まずは一通り必要なパッケージをインストールしておきます。

npm install --save mocha ganache-cli web3

次にtestディレクトリ配下に、NFT.jsを作り、下記を入力します。

const { expect } = require("chai");

describe("初めてのテストを実行します", function () {
  it("必ず失敗するテスト", async function () {
    expect(false).to.equal(true);
  });
});

ではこのテストを実行してみます。

ターミナルで下記コマンドを打ってください。

npx hardhat test

実行した後、下記のように出ていればテスト環境は整っています。

$ npx hardhat test


  初めてのテストを実行します
    1) 必ず失敗するテスト


  0 passing (299ms)
  1 failing

  1) 初めてのテストを実行します
       必ず失敗するテスト:

      AssertionError: expected false to equal true
      + expected - actual

      -false
      +true

テストの雛形の解説

何をしているか解説します。

テストの実行単位

テストコードの実行単位についてです。

describeで関連するテストをまとめています。

テストの最小単位はit単位で実行されます。

下記のようなイメージです。

const { expect } = require("chai");

describe("mintに関するテスト", function () {
  it("mintのテストその1", async function () {
    // ...
  });

  it("mintのテストその2", async function () {
    // ...
  });

  it("mintのテストその3", async function () {
    // ...
  });
});

npx hardhat test コマンドを打つと、testフォルダ配下にあるファイルの全テストが実行されます。

テストの方法

テストは、

  • 何らかの処理をやった後
  • 何かの値が期待値通りになっているか

の繰り返しで成り立ちます。

下記のようなイメージです。

const { expect } = require("chai");

describe("超絶難しいテストを行います", function () {

  it("超絶難しい処理をやった後、その値がtrueであること", async function () {
    const result = // 何かここで難しい関数を呼び出しresultに結果が入るとする
    expect(result).to.equal(true);  // ここでresultがtrueであることを確認!
  });
});

expectの第一引数に、確認したい変数。

それに.to.equal(期待値)をつけることで、確認したい値が期待値になっているかどうかを確認します。

どんなテストもこの構成が基本です。

雛形のテストを成功させてみる

それではtest/NFT.jsを直してテストを成功させてみましょう。

const { expect } = require("chai");

describe("初めてのテストを実行します", function () {
  it("必ず失敗するテスト", async function () {
    // expect(false).to.equal(true);
    expect(true).to.equal(true);
  });
});

expect(false).to.equal(true);expect(true).to.equal(true); に直しただけです。

truetrueであること、みたいな当然なテストをしていますw

では実行してみましょう。

$ npx hardhat test


  初めてのテストを実行します
    ✔ 必ず失敗するテスト (78ms)


  1 passing (91ms)

できたでしょうか?

これでテストの基本は理解できました。

NFTのコントラクトのテストを書く

いよいよコントラクトのテストを書いていきます。

最初は

「スマートコントラクトのmint関数を叩いたら、ウォレットにNFTが紐つけられること」

を確認するテストを書いてみます。

完成版は下記の通りです。

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("mint関連機能のテスト", function () {
  it("mint関数を叩いたら、ウォレットにNFTが紐つけられること", async function () {
    // 1. ソースコードからスマートコントラクトを生成する
    const nft = await ethers.getContractFactory("NFT");

    // 2. テストに使うウォレットアドレスを作成する
    const [owner, addr1, addr2] = await ethers.getSigners();

    // 3. スマートコントラクトをローカルネットワークにデプロイする
    const hardhatToken = await nft.deploy(
      'NFT',
      'NF',
      'ipfs//metadataのCID/',
      'invalid'
    );

    // 4. オーナー以外のウォレットを接続しスマートコントラクトのmint関数を叩く
    await hardhatToken.connect(addr1).mint(1, { value: ethers.utils.parseEther("0.0005") });

    // 5. スマートコントラクトにウォレットアドレスを渡して、NFTのIDリストを取得
    const tokenIds = await hardhatToken.walletOfOwner(addr1.address);

    // 6. ID1 のNFTがウォレットと紐ついていること
    expect(tokenIds).to.deep.equal([ ethers.BigNumber.from("1") ]);
  });
});

各セクションで何をやっているか説明します。

1. ソースコードからスマートコントラクトを生成する

    // 1. ソースコードからスマートコントラクトを生成する
    const nft = await ethers.getContractFactory("NFT");

まずはcontract/NFT.solで定義したスマートコントラクトをコンパイルします。

hardhatパッケージに入っている、ethers.getContractFactory関数にコントラクト名を入れることでコンパイルされたコントラクトを取得できます。

2. テストに使うウォレットアドレスを作成する

    // 2. テストに使うウォレットアドレスを作成する
    const [owner, addr1, addr2] = await ethers.getSigners();

次に、Hardhatが生成するローカルネットワークで使えるウォレットを取得します。

ethers.getSigners()でテストで使えるウォレットの配列を取得することができます。

ウォレットリストの1番目はスマートコントラクトのOwnerアドレス。

以降はOwner以外のアドレスになります。

分割代入をして、Ownerとそれ以外のアドレスをいくつか取得しておきます。

getSigners()で取れるアドレスはデフォルト20個。
10000000000000000000000ETH入っている状態になっています。

3. スマートコントラクトをローカルネットワークにデプロイする

    // 3. スマートコントラクトをローカルネットワークにデプロイする
    const hardhatToken = await nft.deploy(
      'NFT',
      'NF',
      'ipfs//metadataのCID/',
      'invalid'
    );

↑では、スマートコントラクトをHardhatのローカルネットワークにデプロイしています。

1で作ったスマートコントラクトの実体が入っているnftdeploy関数を叩きます。

ところで、deploy関数の引数って何入れてるの?? と思いますよね。

これはNFTのコントラクトのコンストラクタに入る値と同じです。

下記の部分。

contract NFT is ERC721Enumerable, Ownable {
  ...
  constructor(
    string memory _name,
    string memory _symbol,
    string memory _initBaseURI,
    string memory _initNotRevealedUri
  ) ERC721(_name, _symbol) {
    setBaseURI(_initBaseURI);
    setNotRevealedURI(_initNotRevealedUri);
  }

↑はスマートコントラクトをデプロイする際の初期化処理(constructor)です。

NFTの名前(_name)やメタデータで使うCIDのURL(_initBaseURI)を入れる処理です。

deploy関数の引数でこの4つの値を入れ、デプロイしつつ初期化処理を実施しています。

4. オーナー以外のウォレットを接続しスマートコントラクトのmint関数を叩く

    // 4. オーナー以外のウォレットを接続しスマートコントラクトのmint関数を叩く
    await hardhatToken.connect(addr1).mint(1, { value: ethers.utils.parseEther("0.0005") });

まず、hardhatToken.connect(アドレス)部分で指定のアドレスでウォレット接続した状態になります。

そのまま関数をチェーンし、mint関数を叩いています。

mintサイトに行きウォレットを接続 → mintボタンを押したようなイメージですね。

ところで、mint関数には何を渡せばいいのでしょうか?

実はスマートコントラクトに答えが書いてあります。

contracts/NFT.solmint関数を見てください。

  // public
  function mint(uint256 _mintAmount) public payable {
    uint256 supply = totalSupply();
    ...
  }

これを見ると、第一引数にmintする数を入れれば良いことがわかります。

なので下記のように第一引数に1を入れて、1個mintするぞというtransactionをコントラクトに送っています。

mint(1 /* mintする数 */, { value: ethers.utils.parseEther("0.0005") })

さらに、valueに0.0005ETHを指定することで、mint関数を0.0005ETHで叩くぞということを宣言しています。

value: 0.0005 ではなく
ethers.utils.parseEtherを使って専用のオブジェクトに変換する必要があるので注意です

5. スマートコントラクトにウォレットアドレスを渡して、NFTのIDリストを取得

4で特にエラーが出ていなければmintは成功しているはずです。(本当はmintの返り値を見て成功したかどうかを確認すべきですが、ややこしいので割愛します)

次に、mintしたウォレットが持っているNFTのIDを確認してみます。

    // 5. スマートコントラクトにウォレットアドレスを渡して、NFTのIDリストを取得
    const tokenIds = await hardhatToken.walletOfOwner(addr1.address);

walletOfOwner関数にmintしたウォレットのアドレスを入れることで確認できます。

console.log()tokenIdsの中身を見ると、BigNumberというオブジェクトのリストが入っているはずです。

// ID1のNFTを1つ持っているよという意味
[ BigNumber { value: "1" } ]

// 2個NFTを持っていたらこんな風になる。ID1と2のNFTを持っているよという意味
[ BigNumber { value: "1" }, BigNumber { value: "2" } ]

6. ID1 のNFTがウォレットと紐ついていること

    // 6. ID1 のNFTがウォレットと紐ついていること
    expect(tokenIds).to.deep.equal([ ethers.BigNumber.from("1") ]);

最後に、expectを使ってtokenIds[ BigNumber { value: "1" } ]であることを確認しています。

to.equalではなく、to.deep.equalを使っているのに注意です。

配列やオブジェクト同士の比較はdeepを使わないと比較ができません。

今回は配列同士の比較になるため、deepがないと比較ができません。

テストを実行してみる

最後にテストを実行してみます。

npx hardhat testコマンドを叩いてみてください。

$ npx hardhat test


  mint関連機能のテスト
    ✔ mint関数を叩いたら、ウォレットにNFTが紐つけられること (2428ms)


  1 passing (2s)

次のセクションへ

これで

  • ウォレットを接続して
  • mintしたら
  • 自分のウォレットにNFTが入っていること

のテストができました。

次セクションではさらに細かなテストケースを書く方法や、便利な環境設定について書いていきます。

コメント

タイトルとURLをコピーしました