Přeskočit na hlavní obsah

Waffle: Dynamické mockování a testování volání kontraktů

waffle
smart kontrakt účty
solidity
testování
mocking
Středně pokročilý
Daniel Izdebski
14. listopadu 2020
6 minuta čtení

O čem je tento tutoriál?

V tomto tutoriálu se dozvíte, jak:

  • používat dynamické mockování
  • testovat interakce mezi chytrými kontrakty

Předpoklady:

  • už umíte napsat jednoduchý chytrý kontrakt v Solidity
  • orientujete se v JavaScriptu a TypeScriptu
  • už jste absolvovali jiné tutoriály o Waffle nebo o něm už něco málo víte

Dynamické mockování

Proč je dynamické mockování užitečné? Umožňuje nám psát unit testy namísto integračních testů. Co to znamená? Znamená to, že se nemusíme starat o závislosti chytrých kontraktů, a tak je můžeme všechny testovat v naprosté izolaci. Dovolte mi, abych vám přesně ukázal, jak to můžete udělat.

1. Projekt**

Než začneme, musíme si připravit jednoduchý projekt node.js:

mkdir dynamic-mocking
cd dynamic-mocking
mkdir contracts src
yarn init
# nebo pokud používáte npm
npm init

Začněme přidáním závislostí pro typescript a testování - mocha & chai:

yarn add --dev @types/chai @types/mocha chai mocha ts-node typescript
# nebo pokud používáte npm
npm install @types/chai @types/mocha chai mocha ts-node typescript --save-dev

Nyní přidejme Waffle a ethers:

yarn add --dev ethereum-waffle ethers
# nebo pokud používáte npm
npm install ethereum-waffle ethers --save-dev

Struktura vašeho projektu by nyní měla vypadat takto:

1.
2├── contracts
3├── package.json
4└── test

2. Chytrý kontrakt**

Abychom mohli začít s dynamickým mockováním, potřebujeme chytrý kontrakt se závislostmi. Nebojte se, mám to pro vás připravené!

Zde je jednoduchý chytrý kontrakt napsaný v Solidity, jehož jediným účelem je zkontrolovat, zda jsme bohatí. Používá token ERC20 ke kontrole, zda máme dostatek tokenů. Vložte ho do ./contracts/AmIRichAlready.sol.

1pragma solidity ^0.6.2;
2
3interface IERC20 {
4 function balanceOf(address account) external view returns (uint256);
5}
6
7contract AmIRichAlready {
8 IERC20 private tokenContract;
9 uint public richness = 1000000 * 10 ** 18;
10
11 constructor (IERC20 _tokenContract) public {
12 tokenContract = _tokenContract;
13 }
14
15 function check() public view returns (bool) {
16 uint balance = tokenContract.balanceOf(msg.sender);
17 return balance > richness;
18 }
19}
Zobrazit vše

Jelikož chceme použít dynamické mockování, nepotřebujeme celý ERC20, proto používáme rozhraní IERC20 s jedinou funkcí.

Je čas tento kontrakt sestavit! K tomu použijeme Waffle. Nejprve vytvoříme jednoduchý konfigurační soubor waffle.json, který specifikuje možnosti kompilace.

1{
2 "compilerType": "solcjs",
3 "compilerVersion": "0.6.2",
4 "sourceDirectory": "./contracts",
5 "outputDirectory": "./build"
6}

Nyní jsme připraveni sestavit kontrakt s Waffle:

npx waffle

Snadné, že? Ve složce build/ se objevily dva soubory odpovídající kontraktu a rozhraní. Použijeme je později pro testování.

3. Testování**

Vytvořme si soubor s názvem AmIRichAlready.test.ts pro samotné testování. Nejprve se musíme postarat o importy. Budeme je potřebovat později:

1import { expect, use } from "chai"
2import { Contract, utils, Wallet } from "ethers"
3import {
4 deployContract,
5 deployMockContract,
6 MockProvider,
7 solidity,
8} from "ethereum-waffle"

Kromě závislostí JS musíme importovat náš sestavený kontrakt a rozhraní:

1import IERC20 from "../build/IERC20.json"
2import AmIRichAlready from "../build/AmIRichAlready.json"

Waffle používá pro testování chai. Než ho však budeme moci použít, musíme do samotného chai vložit matchery od Waffle:

1use(solidity)

Musíme implementovat funkci beforeEach(), která obnoví stav kontraktu před každým testem. Nejdřív si rozmysleme, co tam budeme potřebovat. K nasazení kontraktu potřebujeme dvě věci: peněženku a nasazený kontrakt ERC20, který předáme jako argument kontraktu AmIRichAlready.

Nejprve si vytvoříme peněženku:

1const [wallet] = new MockProvider().getWallets()

Poté musíme nasadit kontrakt ERC20. Tady je ta záludná část – máme pouze rozhraní. Tohle je ta část, kde nás Waffle přichází zachránit. Waffle má kouzelnou funkci deployMockContract(), která vytváří kontrakt pouze pomocí abi rozhraní:

1const mockERC20 = await deployMockContract(wallet, IERC20.abi)

Nyní s peněženkou i nasazeným ERC20 můžeme pokračovat a nasadit kontrakt AmIRichAlready:

1const contract = await deployContract(wallet, AmIRichAlready, [
2 mockERC20.address,
3])

Tím je naše funkce beforeEach() hotová. Váš soubor AmIRichAlready.test.ts by zatím měl vypadat takto:

1import { expect, use } from "chai"
2import { Contract, utils, Wallet } from "ethers"
3import {
4 deployContract,
5 deployMockContract,
6 MockProvider,
7 solidity,
8} from "ethereum-waffle"
9
10import IERC20 from "../build/IERC20.json"
11import AmIRichAlready from "../build/AmIRichAlready.json"
12
13use(solidity)
14
15describe("Am I Rich Already", () => {
16 let mockERC20: Contract
17 let contract: Contract
18 let wallet: Wallet
19
20 beforeEach(async () => {
21 ;[wallet] = new MockProvider().getWallets()
22 mockERC20 = await deployMockContract(wallet, IERC20.abi)
23 contract = await deployContract(wallet, AmIRichAlready, [mockERC20.address])
24 })
25})
Zobrazit vše

Napišme si první test pro kontrakt AmIRichAlready. Co myslíte, o čem by měl být náš test? Ano, máte pravdu! Měli bychom zkontrolovat, jestli už jsme bohatí :)

Ale počkejte chvilku. Jak bude náš mockovaný kontrakt vědět, jaké hodnoty má vrátit? Neimplementovali jsme žádnou logiku pro funkci balanceOf(). I zde může Waffle pomoci. Náš mockovaný kontrakt má teď několik nových vychytávek:

1await mockERC20.mock.<nameOfMethod>.returns(<value>)
2await mockERC20.mock.<nameOfMethod>.withArgs(<arguments>).returns(<value>)

S touto znalostí můžeme konečně napsat náš první test:

1it("vrátí false, pokud má peněženka méně než 1000000 tokenů", async () => {
2 await mockERC20.mock.balanceOf.returns(utils.parseEther("999999"))
3 expect(await contract.check()).to.be.equal(false)
4})

Rozdělme si tento test na části:

  1. Nastavíme náš mock kontrakt ERC20, aby vždy vracel zůstatek 999999 tokenů.
  2. Zkontrolujeme, zda metoda contract.check() vrací false.

Jsme připraveni to spustit:

Jeden test prochází

Takže test funguje, ale... stále je co zlepšovat. Funkce balanceOf() bude vždy vracet 999999. Můžeme to vylepšit tím, že určíme peněženku, pro kterou by funkce měla něco vrátit – stejně jako u skutečného kontraktu:

1it("vrátí false, pokud má peněženka méně než 1000001 tokenů", async () => {
2 await mockERC20.mock.balanceOf
3 .withArgs(wallet.address)
4 .returns(utils.parseEther("999999"))
5 expect(await contract.check()).to.be.equal(false)
6})

Zatím jsme testovali pouze případ, kdy nejsme dostatečně bohatí. Otestujme místo toho opak:

1it("vrátí true, pokud má peněženka alespoň 1000001 tokenů", async () => {
2 await mockERC20.mock.balanceOf
3 .withArgs(wallet.address)
4 .returns(utils.parseEther("1000001"))
5 expect(await contract.check()).to.be.equal(true)
6})

Spustíte testy...

Dva testy procházejí

...a je to! Zdá se, že náš kontrakt funguje, jak má :)

Testování volání kontraktů

Pojďme si shrnout, co jsme dosud udělali. Otestovali jsme funkčnost našeho kontraktu AmIRichAlready a zdá se, že funguje správně. To znamená, že jsme hotovi, že? Ne tak docela! Waffle nám umožňuje testovat náš kontrakt ještě důkladněji. Ale jak přesně? V arzenálu Waffle jsou matchery calledOnContract() a calledOnContractWith(). Umožní nám zkontrolovat, zda náš kontrakt zavolal mock kontrakt ERC20. Zde je základní test s jedním z těchto matcherů:

1it("kontroluje, zda kontrakt zavolal balanceOf na tokenu ERC20", async () => {
2 await mockERC20.mock.balanceOf.returns(utils.parseEther("999999"))
3 await contract.check()
4 expect("balanceOf").to.be.calledOnContract(mockERC20)
5})

Můžeme jít ještě dál a vylepšit tento test s druhým matcherem, o kterém jsem vám říkal:

1it("kontroluje, zda kontrakt zavolal balanceOf s určitou peněženkou na tokenu ERC20", async () => {
2 await mockERC20.mock.balanceOf
3 .withArgs(wallet.address)
4 .returns(utils.parseEther("999999"))
5 await contract.check()
6 expect("balanceOf").to.be.calledOnContractWith(mockERC20, [wallet.address])
7})

Zkontrolujme, zda jsou testy správné:

Tři testy procházejí

Skvělé, všechny testy jsou zelené.

Testování volání kontraktů s Waffle je super snadné. A tady je ta nejlepší část. Tyto matchery fungují jak s normálními, tak s mockovanými kontrakty! Je to proto, že Waffle zaznamenává a filtruje volání EVM, místo aby vkládal kód, jak je tomu u populárních testovacích knihoven pro jiné technologie.

Cílová rovinka

Výborně! Nyní víte, jak používat Waffle k testování volání kontraktů a dynamickému mockování kontraktů. Existuje mnohem více zajímavých funkcí k objevení. Doporučuji ponořit se do dokumentace Waffle.

Dokumentace Waffle je k dispozici zdeopens in a new tab.

Zdrojový kód pro tento tutoriál naleznete zdeopens in a new tab.

Tutoriály, které by vás také mohly zajímat:

Stránka naposledy aktualizována: 27. února 2024

Byl tento tutoriál užitečný?