前回はテスト対象のスマートコントラクトを書きました。
今回はいよいよテスト対象のコントラクトをテストするコードを書きます。
テストのディレクトリを作成する
まずはスマートコントラクト用のディレクトリを作成しましょう。
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);
に直しただけです。
true
はtrue
であること、みたいな当然なテストをしています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とそれ以外のアドレスをいくつか取得しておきます。
3. スマートコントラクトをローカルネットワークにデプロイする
// 3. スマートコントラクトをローカルネットワークにデプロイする
const hardhatToken = await nft.deploy(
'NFT',
'NF',
'ipfs//metadataのCID/',
'invalid'
);
↑では、スマートコントラクトをHardhatのローカルネットワークにデプロイしています。
1で作ったスマートコントラクトの実体が入っているnft
でdeploy
関数を叩きます。
ところで、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.sol
のmint
関数を見てください。
// 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で叩くぞということを宣言しています。
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" } ]
であることを確認しています。
テストを実行してみる
最後にテストを実行してみます。
npx hardhat test
コマンドを叩いてみてください。
$ npx hardhat test
mint関連機能のテスト
✔ mint関数を叩いたら、ウォレットにNFTが紐つけられること (2428ms)
1 passing (2s)
次のセクションへ
これで
- ウォレットを接続して
- mintしたら
- 自分のウォレットにNFTが入っていること
のテストができました。
次セクションではさらに細かなテストケースを書く方法や、便利な環境設定について書いていきます。
コメント