アルゴリズムとかオーダーとか

仕事で勉強したことなどをまとめてます

web3.jsのmock方法

今回の記事では、DappsのUnit Testでweb3.jsをmockする方法について紹介します。
sinon.jsを用いた方法と、jestを用いた方法の2パターンを紹介します。

本記事で紹介している内容のサンプルプロジェクトも作りましたので参考にしてください。
github.com

テスト対象となるDapps

まずは今回のテスト対象となるDappsを定義します。テストの対象のDappsはよくあるFaucetです。
このFaucetの機能はMyTokenを1アドレスにつき、1日100tokenだけ引き出すというものです。
コードは以下の通り。
gist.github.com

Unit Testのサンプル

上記のFaucetについて、次の3つをテストしたいとします。
1. 最新の1日分のTransferイベントを取得できているか。
2. 当日にすでに支払い済みのアドレスかどうかを判定できるか。
3. 引き出しが100token担っているか。

この場合、シンプルなUnit Testを書くと次のようになります。
gist.github.com

上記のUnit Testを実行するためには、事前条件を満たしたnodeを用意する必要があります。
しかし、実際に事前条件を満たすnodeを用意するのは大変です。ganacheのimport/exportを使う手もありますが、テストしたい対象はあくまでfaucet.jsの機能です。そのため、こういった場合は期待する値を返すようなnodeを準備するのではなく、nodeとのやりとり部分をmockすることが一般的です。つまり、web3.jsをmockしweb3.eth.getBlockNumber()の返す値を自分の好きな値に書き換えられるようにします。

ということで、このテストを修正して、web3.jsが返す値をmockする方法を説明します。

web3.js のmocking方法

sinon.jsを使った方法

sinon.jsでは外部moduleをmockする方法がありません。(多分。探したけどやり方がみつからなかった。)
そのため、sinon.jsを使う場合はweb3.jsに渡すproviderをmockすることで、nodeの動作をmockします。
具体的には次のようなコードになります。
gist.github.com

利点
  • unit testコードのみで完結できます。(と言うかsinon.jsの特性上それしかできない)
  • web3.jsやContractをinjectするわけじゃないので複雑なことはしなくてもよい。
欠点
  • この方法ではJSON-RPCのresponseをシミュレートしないといけないため、mockにinjectするデータが煩雑になるし、直感的ではない。
  • web3.js自体をmockするわけではないため、正しいresponseをInjectしてあげないとweb3.js側で不正なresponseと見なされエラーが発生する。

これらの欠点を解消するために、次にjestを使ってweb3.js自体をmockする方法を紹介します。

jestを使った方法

web3.jsをmockするときの問題点としては、instanciationを可能にしたまま、staticなフィールドもmock objectに定義してあげないといけない点になります。具体的には、

new Web3()

new Web3.providers.HttpProvider()

の両方共をmock後のObjectでも有効にしてあげないと実行時にsyntax errorが発生します。(Javascriptでこういう構造のことなんて言うんだろう?ただのmodule?)

そのため、web3.jsをjestでmockする場合は、Manual Mockを用いてmockingします。つまり、__mocks__/以下に次のようなManual Mockのファイルを作成します。
また、スマートコントラクトについては一般的な定義が難しいため、mock objectをテストコード側からinject可能にします。
Manual Mock用のweb3.jsとそれを用いたUnit Testのコードは以下になります。
gist.github.com

利点
  • web3.js自体をmockするので、直接返して欲しいデータをinject可能。
  • testに必要な最低限のデータを返すようにできる。つまり、実際とは異なるデータ構造でも問題ないので、テストに必要なだけのシンプルなデータ定義するだけでよい。
欠点
  • 呼び出し時の引数チェックなどをしたい場合は、テストコード側でjestのfakeFn()を定義して、mockオブジェクトに渡すようにしないといけない。そのため、spyしたい範囲が増えるとManual Mockのコードを修正する必要がある。
  • アプリのコードから使用されるスマートコントラクトの関数を全て定義しないといけない。

このように、jestを用いた場合は、web3.jsおよび、web3.eth.Contractをmockすることができるため、テストに必要なデータを直接injectすることが可能になり、より直感的なテストがかけるようになります。
その代わりに、アプリコード側から利用されるweb3.jsおよび、Contractの関数を全て定義してあげる必要があるため、mockのためのコード量が増えてしまう欠点があります。

まとめ

今回はweb3.jsを利用しているアプリのUnit Testを書くために、web3.jsをmockする方法についてsinon.jsとjestを用いた2つの方法を解説しました。
web3.jsが高機能というかあまり整理されていない形の定義となっているため、mockするためには一工夫必要なものとなっています。
今回の記事も全てのケースをカバーしているわけではありませんが、web3.jsをmockしたいと考えている人の助けになれば幸いです。