結論
忙しい人のために結論から言うと、Waffleです。
2021/10/26追記
悪いことは言わないから、hardhat(+ethers)使っときなさい
理由
1.テストフレームワークが自由
Truffleはmochaが組み込まれていて、mocha一択ですが、Waffleはavaだろうがjestだろうが自由に選択できます。
2.実行が早い
今時のテストフレームワークは各々のテストをパラレル実行できるオプションがあります。
Waffleはもちろんそれを利用できます。が、Truffleはmocha(もパラレル実行機能はあるのですが、、、)を利用しているにもかかわらず、それができません
3.安定している
Truffleを使うとよくプロセスが残ったままになり、そのまま再実行するとエラーになります。psコマンドで適宜プロセスを切ればいいのですが、そもそもプロセスはちゃんと切れてくれるのが理想ではあります。
4.記述もシンプル
おまけ程度のメリットになります。後述しますが、Truffleに比べWaffleの方がシンプルに記述できると思います。(個人の主観)
注意
最新のWaffleはSolidity0.5系に対応していないので、Solidity自体を0.6系以上で書くか、もしくは古いWaffle(version2.1.0)を使うかしなければなりません。そしてWaffleはバージョンが上がると結構破壊的な修正が入るため、その辺りを考慮して導入してください。
比較
TruffleとWaffleの書き方を比較していきます。
ウォレットの生成
Truffleの場合はcontract(最初にこれを書かないと行けない)のところに記述すれば、テストで利用することができます。
Waffleの場合はテスト内で利用できるウォレットを取得することができます。
Truffleはstring型でそのままウォレットのアドレスが渡されるのですが、Waffleの場合はWolletオブジェクトなので、その辺りの注意が必要です。
[Truffle]
contract('test', ([wallet1, wallet2]) => {
describe('test title', () => {
it('アドレスを表示する', async () => {
console.log(wallet1)
console.log(wallet2)
})
})
})
[Waffle]
import {MockProvider} from "ethereum-waffle"
describe("test", () => {
it("test title.", async () => {
const [wallet1, wallet12] = new MockProvider().getWallets()
console.log(wallet1.address)
console.log(wallet12.address)
})
})
コントラクトの生成
Truffleはartifactsと言うオブジェクトが最初から利用可能になっていて、それを利用してコントラクトをデプロイします。
WaffleはdeployContractと言う関数とbuildした結果出力されるjsonファイルをimportして利用します。
[Truffle]
contract('test', ([wallet1, wallet2]) => {
describe('test title', () => {
it('コントラクトをデプロイする', async () => {
const contract = await artifacts.require('ContractName').new(arg1, arg2, {from: wallet1})
})
})
})
[Waffle]
import {deployContract, MockProvider} from "ethereum-waffle"
import * as TestContarct from "../build/TestContarct.json"
describe("test", () => {
it("test title.", async () => {
const [wallet1, wallet12] = new MockProvider().getWallets()
const contract = await deployContract(wallet1, TestContarct, [arg1, arg2])
})
})
実行wallet変更
実行Walletを指定する場合、Truffleは実行関数の引数に指定します。
Waffleはconnectメソッドを利用します。
[Truffle]
contract('test', ([wallet1, wallet2]) => {
describe('test title', () => {
it('実行ウォレットの変更', async () => {
const contract = await artifacts.require('ContractName').new(arg1, arg2, {from: wallet1})
await contract.func() //何も指定しない場合、wallet1(contractで一番左のアドレス)で実行される
await contract.func({from: wallet2}) //指定すれば指定したウォレットによる実行となる
})
})
})
[Waffle]
import {deployContract, MockProvider} from "ethereum-waffle"
import * as TestContarct from "../build/TestContarct.json"
describe("test", () => {
it("test title.", async () => {
const [wallet1, wallet12] = new MockProvider().getWallets()
const contract = await deployContract(wallet1, TestContarct, [arg1, arg2])
contractOtherWallet = contract.connect(wallet12)
await contract.func() //wallet1の実行
await contractOtherWallet.func() //wallet2の実行
})
})
アサーション
Truffleは最初から組み込まれているアサーション機能を利用します。
Waffleはchaiを利用します。
[Truffle]
contract('test', ([wallet1, wallet2]) => {
describe('test title', () => {
it('アドレスを比較する', async () => {
expect(wallet1).to.be.equal('0x0293674538abcd.....')
})
})
})
[Waffle]
import {expect, use} from "chai"
import {MockProvider, solidity} from "ethereum-waffle"
use(solidity);
describe("test", () => {
it("test title.", async () => {
const [wallet1, wallet12] = new MockProvider().getWallets()
expect(wallet1.address).to.be.equal('0x0293674538abcd.....')
})
})
アサーション(エラーチェック)
Truffleはtry catchなどでエラーを補足します。
Waffleは「use(solidity)」をすることにより、便利なエラーチェック関数を利用することができます。
[Truffle]
contract('test', ([wallet1, wallet2]) => {
describe('test title', () => {
it('エラーが発生することを確認する', async () => {
const contract = await artifacts.require('contract name').new(arg1, arg2, {from: wallet1})
const res = await contract.func().catch((err: Error) => err)
expect(res).to.be.an.instanceof(Error)
expect((res as Error).message).to.include('error message')
})
})
})
[Waffle]
import {expect, use} from "chai";
import {MockProvider, deployContract, solidity} from "ethereum-waffle"
import * as TestContarct from "../build/TestContarct.json"
use(solidity);
describe("test", () => {
it("test title.", async () => {
const [wallet1] = new MockProvider().getWallets()
const contract = await deployContract(wallet1, TestContarct, [arg1, arg2])
await expect(contract.func()).to.be.revertedWith('error message')
})
})
イベントの取得
Truffleはtruffle-assertionsというライブラリを使うといい感じにできます。
Waffleは関数実行時についでにチェックする方法と、フィルターを使った、後からチェックする方法があります。
[Truffle]
import {assert, use} from "chai"
const truffleAssert = require('truffle-assertions');
contract('test', ([wallet1, wallet2]) => {
describe('test title', () => {
it('イベントをチェックする', async () => {
const contract = await artifacts.require('contract name').new(arg1, arg2, {from: wallet1})
const transactionInfo = await contract.func()
truffleAssert.eventEmitted(transactionInfo, 'EventName', (ev) => {
return ev.user === 'hogehoge' && !ev.age.eq(5)
})
// イベントが発生しないことをチェックする方法
truffleAssert.eventNotEmitted(transactionInfo, 'EventName')
})
})
})
[Waffle]
import {expect, use} from "chai"
import {MockProvider, deployContract, solidity} from "ethereum-waffle"
import * as TestContarct from "../build/TestContarct.json"
use(solidity)
describe("test", () => {
it("test title.", async () => {
const [wallet1] = new MockProvider().getWallets()
const contract = await deployContract(wallet1, TestContarct, [arg1, arg2])
// イベントのチェック
await expect(expect(contract.func()).to.be.revertedWith('error message'))
.to.emit(contract, 'EventName').withArgs(
'hogehoge',
10
)
// イベントのデータ自体も取得できる
const filter = contract.filters.EventName()
const events = await contract.queryFilter(filter)
console.log(events)
})
})
関数実行
Truffleの場合はweb3をimportして実行します。web3自体はnewせずともテスト用のganechaに勝手に接続されて、使える状態になります。
Waffleの場合はproviderを利用するだけです。simpleでいいです。
[Truffle]
import Web3 from 'web3'
contract('test', ([wallet1, wallet2]) => {
describe('test title', () => {
it('ブロックを進める', async () => {
await new Promise(function (resolve) {
web3.currentProvider.send(
{
jsonrpc: '2.0',
method: 'evm_mine',
params: [],
id: 0,
},
resolve
)
})
})
})
})
[Waffle]
import {MockProvider} from "ethereum-waffle"
describe("test", () => {
it("test title.", async () => {
const provider = new MockProvider()
await provider.send('evm_mine', [])
})
})