Waffle: Mocking dinamis dan percobaan memanggil kontrak pintar
Apa yang akan di pelajari pada tutorial ini?
Pada tutorial ini, Anda akan belajar cara:
- menggunakan mocking dinamis
- menguji interaksi antara kontrak pintar
Asumsi:
- Anda telah mengetahui bagaimana cara menulis kontrak pintar sederhana dalam
Solidity
- Anda terbiasa dengan
JavaScript
danTypeScript
- Anda telah menyelesaikan tutorial
Waffle
lainnya atau mengetahui beberapa hal tentangnya
Mocking dinamis
Mengapa mocking dinamis sangat berguna? Baiklah, hal ini mengizinkan kita menulis tes unit ketimbang tes yang terintegrasi. Apa maksudnya? Hal ini berarti bahwa kita tidak perlu khawatir tentang dependensi kontrak pintar, sehingga kita bisa menguji semuanya secara terisolasi. Saya akan menunjukan bagaimana cara Anda melakukannya.
1. Proyek
Sebelum memulai, kita perlu menyiapkan proyek node.js sederhana:
$ mkdir dynamic-mocking$ cd dynamic-mocking$ mkdir contracts src$ yarn init# or if you're using npm$ npm init
Mari kita mulai dengan menambah typescript dan menguji dependensi - mocha dan chai:
$ yarn add --dev @types/chai @types/mocha chai mocha ts-node typescript# atau jika anda menggunakan npm$ npm install @types/chai @types/mocha chai mocha ts-node typescript --save-dev
Sekarang mari tambahkan Waffle
dan ether
:
$ yarn add --dev ethereum-waffle ethers# atau jika anda menggunakan npm$ npm install ethereum-waffle ethers --save-dev
Struktur proyek Anda seharusnya terlihat seperti ini:
1.2├── contracts3├── package.json4└── test
2. Kontrak pintar
Untuk memulai mocking dinamis, kita membutuhkan kontrak pintar dengan dependensi. Tenang, saya akan membantu!
Berikut adalah kontrak pintar sederhana yang ditulis dalam Solidity
yang tujuannya hanya memeriksa apakah kita kaya. Kontrak pintar ini menggunakan token ERC20 untuk memeriksa apakah kita memiliki token yang cukup. Letakan pada ./contracts/AmIRichAlready.sol
.
1pragma solidity ^0.6.2;23interface IERC20 {4 function balanceOf(address account) external view returns (uint256);5}67contract AmIRichAlready {8 IERC20 private tokenContract;9 uint public richness = 1000000 * 10 ** 18;1011 constructor (IERC20 _tokenContract) public {12 tokenContract = _tokenContract;13 }1415 function check() public view returns (bool) {16 uint balance = tokenContract.balanceOf(msg.sender);17 return balance > richness;18 }19}Tampilkan semuaSalin
Karena kita mau menggunakan mocking dinamis, kita tidak memerlukan seluruh ERC20, itulah mengapa kita menggunakan antarmuka IERC20 dengan hanya satu fungsi.
Saatnya membuat kontrak ini! Untuk itu, kita akan menggunakan Waffle
. Pertama, kita akan membuat file konfigurasi waffle.json
sederhana yang menentukan pilihan kompilasi.
1{2 "compilerType": "solcjs",3 "compilerVersion": "0.6.2",4 "sourceDirectory": "./contracts",5 "outputDirectory": "./build"6}Salin
Sekarang, kita sudah siap untuk membuat kontrak menggunakan Waffle:
$ npx waffle
Mudah, bukan? Dalam folder build/
, dua file sesuai dengan kontrak dan antar mukanya muncul. Kita akan menggunakannya nanti untuk percobaan.
3. Pengujian
Mari kita buat file bernama AmIRichAlready.test.ts
untuk tes yang sebenarnya. Pertama, kita harus menangani hasil impor. Kita akan membutuhkannya nanti:
1import { expect, use } from "chai"2import { Contract, utils, Wallet } from "ethers"3import {4 deployContract,5 deployMockContract,6 MockProvider,7 solidity,8} from "ethereum-waffle"
Kecuali untuk dependensi JS, kita harus mengimpor kontrak dan antar muka yang kita bangun:
1import IERC20 from "../build/IERC20.json"2import AmIRichAlready from "../build/AmIRichAlready.json"
Waffle menggunakan chai
untuk pengujian. Namun, sebelum kita menggunakannya, kita harus menginjeksi matcher Waffle kedalam chai:
1use(solidity)
Kita harus mengimplementasikan fungsi beforeEach()
yang akan mengatur ulang state kontrak sebelum setiap tes dimulai. Pertama-tama mari kita pikirkan apa yang kita butuhkan untuk itu. Untuk menggunakan kontrak, kita membutuhkan dua hal: dompet dan kontrak ERC20 yang digunakan untuk meneruskannya sebagai argumen kontrak AmIRichAlready
.
Pertama, kita buat dompetnya:
1const [wallet] = new MockProvider().getWallets()
Selanjutnya, kita harus menggunakan kontrak ERC20. Ini bagian sulitnya - kita hanya memiliki satu antarmuka. Di sinilah peran di mana Waffle datang menyelamatkan kita. Waffle memiliki fungsi deployMockContract()
ajaib yang membuat kontrak dengan hanya menggunakan abi dari antarmuka:
1const mockERC20 = await deployMockContract(wallet, IERC20.abi)
Sekarang dengan dompet maupun ERC20 yang digunakan, kita akan lanjutkan dan menggunakan kontrak AmIRichAlready
:
1const contract = await deployContract(wallet, AmIRichAlready, [2 mockERC20.address,3])
Secara keseluruhan, fungsi beforeEach()
kita telah selesai. Sejauh ini file AmIRichAlready.test.ts
Anda seharusnya telihat seperti ini:
1import { expect, use } from "chai"2import { Contract, utils, Wallet } from "ethers"3import {4 deployContract,5 deployMockContract,6 MockProvider,7 solidity,8} from "ethereum-waffle"910import IERC20 from "../build/IERC20.json"11import AmIRichAlready from "../build/AmIRichAlready.json"1213use(solidity)1415describe("Am I Rich Already", () => {16 let mockERC20: Contract17 let contract: Contract18 let wallet: Wallet1920 beforeEach(async () => {21 ;[wallet] = new MockProvider().getWallets()22 mockERC20 = await deployMockContract(wallet, IERC20.abi)23 contract = await deployContract(wallet, AmIRichAlready, [mockERC20.address])24 })25})Tampilkan semua
Mari kita tulis tes pertama ke kontrak AmIRichAlready
. Menurut Anda, tentang apa seharusnya tes kita? Ya, Anda benar! Kita harus memeriksa apakah kita sudah kaya :)
Tapi tunggu dulu. Bagaimana kontrak mocked kita tahu nilai apa yang dikembalikan? Kita belum mengimplementasikan logika apa pun untuk fungsi balanceOf()
. Sekali lagi, Waffle bisa membantu kita di sini. Kontrak mocked kita memiliki hal baru yang menarik sekarang:
1await mockERC20.mock.<nameOfMethod>.returns(<value>)2await mockERC20.mock.<nameOfMethod>.withArgs(<arguments>).returns(<value>)
Dengan pengetahuan ini, akhirnya kita bisa menulis tes pertama kita:
1it("returns false if the wallet has less than 1000000 tokens", async () => {2 await mockERC20.mock.balanceOf.returns(utils.parseEther("999999"))3 expect(await contract.check()).to.be.equal(false)4})
Mari kita pecahkan tes ini ke dalam bagian-bagian:
- Kita atur kontrak mock ERC20 kita untuk selalu mengembalikan saldo 999999 token.
- Periksa apakah metode
contract.check()
mengembalikan nilaifalse
.
Kita siap untuk menyalakannya:
Jadi testnya bekerja, namun... masih ada sedikit ruang untuk peningkatan. Fungsi balanceOf()
akan selalu mengembalikan 99999. Kita bisa meningkatkannya dengan menentukan dompet yang ke mana fungsinya harus mengembalikan sesuatu - sama seperti kontrak sungguhan:
1it("returns false if the wallet has less than 1000001 tokens", async () => {2 await mockERC20.mock.balanceOf3 .withArgs(wallet.address)4 .returns(utils.parseEther("999999"))5 expect(await contract.check()).to.be.equal(false)6})
Sejauh ini, kita hanya mencoba kasus di mana kita tidak cukup kaya. Mari kita coba kebalikannya:
1it("returns true if the wallet has at least 1000001 tokens", async () => {2 await mockERC20.mock.balanceOf3 .withArgs(wallet.address)4 .returns(utils.parseEther("1000001"))5 expect(await contract.check()).to.be.equal(true)6})
Anda jalankan tesnya...
....dan ini dia! Kontrak kita tampak berjalan sebagaimana mestinya :)
Menguji pemanggilan kontrak
Mari kita ringkas apa yang telah kita lakukan sejauh ini. Kita telah menguji fungsionalitas kontrak AmIRichAlready
kita dan tampaknya bekerja dengan benar. Artinya, kita telah selesai, bukan? Belum selesai! Waffle memungkinkan kita menguji kontrak bahkan lebih jauh lagi. Tapi, bagaimana persisnya? Dalam gudang senjata Waffle, ada matcher calledOnContract()
dan calledOnContractWith()
. Mereka akan memungkinkan kita memeriksa apakah kontrak kita memanggil kontrak mock ERC20. Berikut adalah tes dasarnya dengan salah satu matcher ini:
1it("checks if contract called balanceOf on the ERC20 token", async () => {2 await mockERC20.mock.balanceOf.returns(utils.parseEther("999999"))3 await contract.check()4 expect("balanceOf").to.be.calledOnContract(mockERC20)5})
Kita bahkan bisa melangkah lebih jauh dan meningkatkan tes ini dengan matcher lain yang saya sebutkan sebelumnya:
1it("checks if contract called balanceOf with certain wallet on the ERC20 token", async () => {2 await mockERC20.mock.balanceOf3 .withArgs(wallet.address)4 .returns(utils.parseEther("999999"))5 await contract.check()6 expect("balanceOf").to.be.calledOnContractWith(mockERC20, [wallet.address])7})
Mari kita periksa apakah tesnya benar:
Hebat, semua test berwarna hijau.
Menguji pemanggilan kontrak dengan Waffle sangatlah mudah. Dan inilah bagian terbaiknya. Matcher ini bekerja baik dalam kontrak normal dan mocked! Itu karena Waffle mencatat dan menyaring pemanggilan EVM ketimbang menginjeksi kode, seperti dalam kasus pustaka pengujian populer untuk teknologi lainnya.
Garis Akhir
Selamat! Sekarang Anda tahu cara menggunakan Waffle untuk menguji pemanggilan kontrak dan kontrak mock secara dinamis. Ada fitur yang jauh lebih menarik untuk ditemukan. Saya menyarankan menyelam ke dalam dokumentasi Waffle.
Dokumentasi Waffle tersedia di sini(opens in a new tab).
Sumber kode untuk tutorial ini bisa ditemukan di sini(opens in a new tab).
Tutorial yang mungkin juga Anda minati:
Terakhir diedit: @nhsz(opens in a new tab), 27 Februari 2024