Philosophy
-
Simpler
Minimalistic, fewer dependencies.
-
Sweeter
Nicer syntax, easy to learn, and easy to extend.
-
Faster
Faster contract compilation and tests execution.
Features
Sweet matchers
await expect(token.transfer(walletTo.address, 7))
.to.emit(token, 'Transfer')
.withArgs(wallet.address, walletTo.address, 7);
await expect(token.transfer(walletTo.address, 1007)).to.be.reverted;
Mocking and testing expectations
mockERC20 = await deployMockContract(wallet, IERC20.abi);
await mockERC20.mock.balanceOf.returns(parseEther('999999'));
expect('balanceOf').to.be
.calledOnContractWith(ERC20, [wallet.address]);
Fixtures
const {token, wallet} = await loadFixture(tokenFixture);
expect(await token.balanceOf(wallet.address)).to.equal(1000);
-
Compiles Solidity & Vyper
-
Write tests with ENS
-
Works with Hardhat
Example code
Below is an example contract, that delivers from standard ERC20 token:
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// Example class - a mock class using delivering from ERC20
contract BasicToken is ERC20 {
constructor(uint256 initialBalance) ERC20("Basic", "BSC") public {
_mint(msg.sender, initialBalance);
}
}
Belows is example test written for the contract above written with Waffle.
import {expect, use} from 'chai';
import {Contract} from 'ethers';
import {deployContract, MockProvider, solidity} from 'ethereum-waffle';
import BasicToken from '../build/BasicToken.json';
use(solidity);
describe('BasicToken', () => {
const [wallet, walletTo] = new MockProvider().getWallets();
let token: Contract;
beforeEach(async () => {
token = await deployContract(wallet, BasicToken, [1000]);
});
it('Assigns initial balance', async () => {
expect(await token.balanceOf(wallet.address)).to.equal(1000);
});
it('Transfer adds amount to destination account', async () => {
await token.transfer(walletTo.address, 7);
expect(await token.balanceOf(walletTo.address)).to.equal(7);
});
it('Transfer emits event', async () => {
await expect(token.transfer(walletTo.address, 7))
.to.emit(token, 'Transfer')
.withArgs(wallet.address, walletTo.address, 7);
});
it('Can not transfer above the amount', async () => {
await expect(token.transfer(walletTo.address, 1007)).to.be.reverted;
});
it('Can not transfer from empty account', async () => {
const tokenFromOtherWallet = token.connect(walletTo);
await expect(tokenFromOtherWallet.transfer(wallet.address, 1))
.to.be.reverted;
});
it('Calls totalSupply on BasicToken contract', async () => {
await token.totalSupply();
expect('totalSupply').to.be.calledOnContract(token);
});
it('Calls balanceOf with sender address on BasicToken contract', async () => {
await token.balanceOf(wallet.address);
expect('balanceOf').to.be.calledOnContractWith(token, [wallet.address]);
});
});
More examples available on our GitHub
How to install
To start using with npm, type:
npm i ethereum-waffle
To start using with yarn, type:
yarn add ethereum-waffle
Patterns and solutions
Materials
Blogpost on Ethworks blog
- Introducing Waffle support for Jest (experimental)
- Smart contract testing with ENS and Waffle
- 🧇 Waffle 3.0 released! With ethers.js 5.0 and more!
- New Waffle matcher: expect().to.be.calledOnContract()
- Mocking Solidity smart contracts with Waffle
- Introducing Waffle
- Waffle 2.0 released
- Smart Contracts testing still sucks, how we want to make it better