Waffle: Dynamické mockování a testování volání kontraktů
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
JavaScriptuaTypeScriptu - už jste absolvovali jiné tutoriály o
Wafflenebo 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-mockingcd dynamic-mockingmkdir contracts srcyarn init# nebo pokud používáte npmnpm initZač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 npmnpm install @types/chai @types/mocha chai mocha ts-node typescript --save-devNyní přidejme Waffle a ethers:
yarn add --dev ethereum-waffle ethers# nebo pokud používáte npmnpm install ethereum-waffle ethers --save-devStruktura vašeho projektu by nyní měla vypadat takto:
1.2├── contracts3├── package.json4└── test2. 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;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}Zobrazit všeJelikož 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 waffleSnadné, ž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"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})Zobrazit všeNapiš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:
- Nastavíme náš mock kontrakt ERC20, aby vždy vracel zůstatek 999999 tokenů.
- Zkontrolujeme, zda metoda
contract.check()vracífalse.
Jsme připraveni to spustit:
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.balanceOf3 .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.balanceOf3 .withArgs(wallet.address)4 .returns(utils.parseEther("1000001"))5 expect(await contract.check()).to.be.equal(true)6})Spustíte testy...
...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.balanceOf3 .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é:
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


