Тестування простого смарт-контракту з бібліотекою Waffle
У цьому підручнику ви дізнаєтеся, як
- Перевіряти зміни балансу гаманця
- Тестувати емісію подій із зазначеними аргументами
- Перевіряти, що транзакцію було повернуто
Припущення
- Ви можете створити новий проєкт на JavaScript або TypeScript
- У вас є базовий досвід роботи з тестами в JavaScript
- Ви використовували менеджери пакунків, як-от yarn або npm
- Ви володієте базовими знаннями про смарт-контракти та Solidity
Початок роботи
У цьому посібнику демонструється налаштування та запуск тестування за допомогою yarn, але ви можете використовувати й npm. Я надам відповідні посилання на офіційну документаціюopens in a new tab Waffle.
Встановлення залежностей
Додайтеopens in a new tab залежності ethereum-waffle та typescript до залежностей для розробки (dev dependencies) вашого проєкту.
yarn add --dev ethereum-waffle ts-node typescript @types/jestПриклад смарт-контракту
Під час цього посібника ми будемо працювати над простим прикладом смарт-контракту — EtherSplitter. Він мало що робить, окрім як дозволяє будь-кому надіслати трохи wei та розділити їх порівну між двома попередньо визначеними одержувачами.
Функція split вимагає, щоб кількість wei була парною, інакше транзакція буде повернута. Для обох одержувачів він виконує переказ wei, після чого відбувається емісія події Transfer.
Розмістіть фрагмент коду EtherSplitter у src/EtherSplitter.sol.
1pragma solidity ^0.6.0;23contract EtherSplitter {4 address payable receiver1;5 address payable receiver2;67 event Transfer(address from, address to, uint256 amount);89 constructor(address payable _address1, address payable _address2) public {10 receiver1 = _address1;11 receiver2 = _address2;12 }1314 function split() public payable {15 require(msg.value % 2 == 0, 'Uneven wei amount not allowed');16 receiver1.transfer(msg.value / 2);17 emit Transfer(msg.sender, receiver1, msg.value / 2);18 receiver2.transfer(msg.value / 2);19 emit Transfer(msg.sender, receiver2, msg.value / 2);20 }21}Показати всеКомпіляція контракту
Щоб скомпілюватиopens in a new tab контракт, додайте наступний запис до файлу package.json:
1"scripts": {2 "build": "waffle"3 }Далі створіть файл конфігурації Waffle у кореневому каталозі проєкту — waffle.json — а потім вставте туди таку конфігурацію:
1{2 "compilerType": "solcjs",3 "compilerVersion": "0.6.2",4 "sourceDirectory": "./src",5 "outputDirectory": "./build"6}Запустіть yarn build. У результаті з’явиться каталог build зі скомпільованим контрактом EtherSplitter у форматі JSON.
Налаштування тестування
Тестування за допомогою Waffle вимагає використання Chai matchers та Mocha, тому вам потрібно додатиopens in a new tab їх до свого проєкту. Оновіть файл package.json і додайте запис test у розділ scripts:
1"scripts": {2 "build": "waffle",3 "test": "export NODE_ENV=test && mocha -r ts-node/register 'test/**/*.test.ts'"4 }Якщо ви хочете виконатиopens in a new tab свої тести, просто запустіть yarn test.
Тестування
Тепер створіть каталог test і створіть новий файл test\EtherSplitter.test.ts.
Скопіюйте фрагмент коду нижче та вставте його в наш файл тестування.
1import { expect, use } from "chai"2import { Contract } from "ethers"3import { deployContract, MockProvider, solidity } from "ethereum-waffle"4import EtherSplitter from "../build/EtherSplitter.json"56use(solidity)78describe("Розподілювач ефіру", () => {9 const [sender, receiver1, receiver2] = new MockProvider().getWallets()10 let splitter: Contract1112 beforeEach(async () => {13 splitter = await deployContract(sender, EtherSplitter, [14 receiver1.address,15 receiver2.address,16 ])17 })1819 // додайте сюди тести20})Показати всеКілька слів перед тим, як ми почнемо.
MockProvider створює імітовану версію блокчейну. Він також надає імітовані гаманці, які слугуватимуть нам для тестування контракту EtherSplitter. Ми можемо отримати до десяти гаманців, викликавши метод getWallets() у провайдера. У цьому прикладі ми отримуємо три гаманці: для відправника та для двох одержувачів.
Далі ми оголошуємо змінну з назвою splitter — це наш імітований контракт EtherSplitter. Він створюється перед кожним виконанням окремого тесту за допомогою методу deployContract. Цей метод імітує розгортання контракту з гаманця, переданого як перший параметр (у нашому випадку — гаманець відправника). Другий параметр — це ABI та байт-код тестованого контракту. Ми передаємо туди JSON-файл скомпільованого контракту EtherSplitter з каталогу build. Третій параметр — це масив з аргументами конструктора контракту, якими в нашому випадку є дві адреси одержувачів.
Зміна балансів
Спочатку ми перевіримо, чи метод split дійсно змінює баланси гаманців одержувачів. Якщо ми розділимо 50 wei з рахунку відправника, то очікуємо, що баланси обох одержувачів збільшаться на 25 wei. Ми будемо використовувати матчер changeBalances з Waffle:
1it("Змінює баланси рахунків", async () => {2 await expect(() => splitter.split({ value: 50 })).to.changeBalances(3 [receiver1, receiver2],4 [25, 25]5 )6})Як перший параметр матчера ми передаємо масив гаманців одержувачів, а як другий — масив очікуваних приростів на відповідних рахунках.
Якби ми хотіли перевірити баланс одного конкретного гаманця, ми могли б також використати матчер changeBalance, який не вимагає передачі масивів, як у прикладі нижче:
1it("Змінює баланс рахунку", async () => {2 await expect(() => splitter.split({ value: 50 })).to.changeBalance(3 receiver1,4 255 )6})Зверніть увагу, що в обох випадках (changeBalance і changeBalances) ми передаємо функцію split як функцію зворотного виклику, оскільки матчеру потрібно отримати доступ до стану балансів до та після виклику.
Далі ми перевіримо, чи була згенерована подія Transfer після кожного переказу wei. Ми звернемося до іншого матчера з Waffle:
Emit
1it("Генерує подію під час переказу першому одержувачу", async () => {2 await expect(splitter.split({ value: 50 }))3 .to.emit(splitter, "Transfer")4 .withArgs(sender.address, receiver1.address, 25)5})67it("Генерує подію під час переказу другому одержувачу", async () => {8 await expect(splitter.split({ value: 50 }))9 .to.emit(splitter, "Transfer")10 .withArgs(sender.address, receiver2.address, 25)11})Показати всеМатчер emit дозволяє нам перевірити, чи згенерував контракт подію під час виклику методу. Як параметри для матчера emit, ми надаємо імітований контракт, який, за нашими прогнозами, згенерує подію, разом із назвою цієї події. У нашому випадку імітований контракт — це splitter, а назва події — Transfer. Ми також можемо перевірити точні значення аргументів, з якими була згенерована подія — ми передаємо стільки аргументів до матчера withArgs, скільки очікує наше оголошення події. У випадку контракту EtherSplitter, ми передаємо адреси відправника та одержувача разом із сумою переказаних wei.
revertedWith
В останньому прикладі ми перевіримо, чи була транзакція повернута у випадку непарної кількості wei. Ми будемо використовувати матчер revertedWith:
1it("Повертає транзакцію, якщо кількість wei непарна", async () => {2 await expect(splitter.split({ value: 51 })).to.be.revertedWith(3 "Uneven wei amount not allowed"4 )5})Тест, якщо він пройде успішно, запевнить нас, що транзакція дійсно була повернута. Однак, також має бути точна відповідність між повідомленнями, які ми передали в операторі require, і повідомленням, яке ми очікуємо в revertedWith. Якщо ми повернемося до коду контракту EtherSplitter, то в операторі require для суми wei ми надаємо повідомлення: 'Uneven wei amount not allowed'. Це відповідає повідомленню, яке ми очікуємо в нашому тесті. Якби вони не були однаковими, тест не пройшов би.
Вітаємо!
Ви зробили свій перший великий крок до тестування смарт-контрактів за допомогою Waffle!
Останні оновлення сторінки: 17 грудня 2025 р.