# Smart Contract Testing with Waffle 3

Author: Markus Waas

Published: 2020-10-03T19:24:18.000Z

Updated: 2026-09-13T14:25:30.000Z

Source: [https://soliditydeveloper.com/waffle](<https://soliditydeveloper.com/waffle>)

## Compatibility and review

Before you start

Historical Waffle 3/Buidler guide with ethers v5-style APIs. Buidler is now Hardhat, and modern Hardhat/ethers setups differ. The ENS section uses a local mock registry; it does not register public DNS names or current ENS names.

[Official reference](<https://blog.nomic.foundation/buidler-has-evolved-introducing-hardhat-4bccd13bc931/>)

[Waffle](<https://getwaffle.io/>) has been a relatively recent new testing framework, but has gained a lot of popularity thanks to its simplicity and speed. Is it worth a try? Absolutely. I wouldn't run and immediately convert every project to it, but you might want to consider it for new ones. It's also actively being developed and continuously improving.

## 1. Requirements: Install the tools

First let's create a new project and initialize it with [Buidler](<https://buidler.dev/>).

```bash
$ npm init
$ npm install --save-dev @nomiclabs/buidler
```

Now create a Buidler project by running `npx buidler` and choose the example project. Afterwards, you can install the required dependencies.

```javascript
$ npm install --save-dev @nomiclabs/buidler-waffle ethereum-waffle chai @nomiclabs/buidler-ethers ethers
```

We are almost finished with the setup. Let's change the package.json scripts. Add the `npx buidler test` command to your package.json scripts and see if everything is working by running `npm test`.

## 2. Waffle testing

The default contract created by Buidler is a simple Greeter contract. On the right is the extended version of it:

- You can set and read a `greeting` variable.
- You see a way to utilize the Buidler `console.log`.
- Added a `number` variable and a respective setter.

This will be our example contract for the testing. The basic example test is given below under `test/sample-test.js`:

```javascript
const { expect } = require("chai");

describe("Greeter", () => {
  it("Should return the new greeting", async () => {
    const Greeter = await ethers.getContractFactory("Greeter");
    const greeter = await Greeter.deploy("Hello world!");
    expect(await greeter.greet()).to.equal("Hello world!");

    await greeter.setGreeting("Hola, mundo!");
    expect(await greeter.greet()).to.equal("Hola, mundo!");
  });
});
```

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.8;

import "@nomiclabs/buidler/console.sol";

contract Greeter {
    string greeting;
    uint256 public number;

    constructor(string memory _greeting) public {
        console.log("Deploying a Greeter with greeting:", _greeting);
        greeting = _greeting;
    }

    function greet() public view returns (string memory) {
        return greeting;
    }

    function setGreeting(string memory _greeting) public {
        console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
        greeting = _greeting;
    }

    function setNumber(uint256 _number) public {
        number = _number;
    }
}
```

You can either use the Buidler functionality of `getContractFactory`. This is an [injected function](<https://github.com/nomiclabs/buidler/tree/development/packages/buidler-ethers#helpers>) by Buidler to help you. Or you could use the native Waffle `deployContract` which is available automatically as [waffle.deployContract](<https://github.com/nomiclabs/buidler/tree/development/packages/buidler-waffle#environment-extensions>) in Buidler. If you wanted to go down that path, make sure to the correct path to your ABI (`./artifacts` in Buidler), see [here](<https://ethereum-waffle.readthedocs.io/en/latest/getting-started.html#writing-tests>) for an example.

### 2.1 Waffle matching helpers

The test above shows you the basic setup, but doesn't really use a lot of Waffle features yet. One very useful feature present in Waffle is the [automatic matching](<https://ethereum-waffle.readthedocs.io/en/latest/matchers.html>). They are similar what the [@openzeppelin/test-helpers](<https://docs.openzeppelin.com/test-helpers/0.5/>) are for Truffle.

Available matchers exist for

- [BigNumbers](<https://ethereum-waffle.readthedocs.io/en/latest/matchers.html#bignumbers>)
- [Events](<https://ethereum-waffle.readthedocs.io/en/latest/matchers.html#emitting-events>)
- [Contract calls](<https://ethereum-waffle.readthedocs.io/en/latest/matchers.html#called-on-contract>) (doesn't exist for Truffle)
- [Reverts](<https://ethereum-waffle.readthedocs.io/en/latest/matchers.html#revert>)
- [Balance changes](<https://ethereum-waffle.readthedocs.io/en/latest/matchers.html#change-balance>)

Now let's use the BigNumbers matcher as example. You don't have to do anything, you can just the `.to.equal` and it will work for BigNumbers as well.

Let's also move the deployment into a [before](<https://mochajs.org/#hooks>) hook. This way our contract is only deployed once.

```javascript
const { expect } = require("chai");

describe("Greeter", () => {
  let greeter;

  before(async () => {
    const Greeter = await ethers.getContractFactory("Greeter");
    greeter = await Greeter.deploy("Hello world!");
    await greeter.deployed();
  });

  it("Should return the new greeting", async () => {
    expect(await greeter.greet()).to.equal("Hello world!");

    await greeter.setGreeting("Hola, mundo!");
    expect(await greeter.greet()).to.equal("Hola, mundo!");
  });

  it("Should set the number", async () => {
    expect(await greeter.number()).to.equal("0");

    await greeter.setNumber(123);
    expect(await greeter.number()).to.equal("123");
  });
});
```

### 2.2 Waffle fixtures to speed up running time

Now you may have noticed that there's one problem with using the before hook as done above. What do you think would happen if we added the following line to the second test (*Should set the number*)?

```javascript
 expect(await greeter.greet()).to.equal("Hello world!");
```

That's right, it would fail. Why?

Because the `before` hook is run only once, so in our first test with `setGreeting("Hola, mundo!")` we actually change the state also for the second test. The easiest solution?

Just use `beforeEach` instead of `before`. This works as it deploys a new contract before each test. The drawback?

Performance!

![fast](<https://cdn0.scrvt.com/b095ee27d37b3d7b6b150adba9ac6ec8/e72178a4fda9501d/2212482ec347/v/bb51594111c7/fast.jpeg>)

This is where Waffle comes in. We can define fixtures that create certain scenarios. For example our fixture:

```javascript
async function fixture([wallet]) {
  const greeter = await deployContract(
    wallet, Greeter, ["Hello world!"]
  );
  return { greeter, wallet };
}
```

This fixture just deploys our Greeter contract, but with a catch. A snapshot of our blockchain is taken after running this fixture. So afterwards with `loadFixture` we don't actually run time-intensive redeploments.

Rather we simply load the previously stored snapshot of the blockchain. This makes our runtimes significantly faster than using `beforeEach`.

**Please note**: Unfortuntately currently the buidler-integrated `loadFixture` [is broken](<https://discord.com/channels/750408878008827925/751111080809463938/753537480283914291>) as discovered by [PaulRBerg](<https://twitter.com/PaulRBerg>). The solution is to use `createFixtureLoader` as shown on the right.

```javascript
const { expect } = require("chai");
const { deployContract, MockProvider } = require("ethereum-waffle");
const { waffle } = require("@nomiclabs/buidler");
const Greeter = require("../artifacts/Greeter.json");

const myProvider = new MockProvider();
const loadFixture = waffle.createFixtureLoader(
  myProvider.getWallets(),
  myProvider
);

describe("Greeter", () => {
  async function fixture([wallet]) {
    const greeter = await deployContract(wallet, Greeter, ["Hello world!"]);
    return { greeter, wallet };
  }

  it("Should return the new greeting once it's changed", async () => {
    const { greeter } = await loadFixture(fixture);

    expect(await greeter.greet()).to.equal("Hello world!");

    await greeter.setGreeting("Hola, mundo!");
    expect(await greeter.greet()).to.equal("Hola, mundo!");
  });
});
```

### 2.3 Waffle mocking

On top of [mocking as with Truffle](<https://soliditydeveloper.com/mocking-contracts>)[https://soliditydeveloper.com/mocking-contracts](<https://soliditydeveloper.com/mocking-contracts>)contracts, in Waffle you can mock contract calls automatically. It's like an integrated [gnosis/mock-contract](<https://github.com/gnosis/mock-contract>). Instead of `deployContract` you just use `deployMockContract` when deploying in your tests. Then you have the ability to change the return values of contract calls.

One drawback is that this works only when calling a contract within a contract. So given our Greeter example, to use the feature we need to add a GreetManager contract. Let's also add a string parameter to the `greet` function. As you can see, we can

- mock every call using `mockGreeter.mock.greet.returns("mockedReturn 1")`
- or mock depending on the passed arguments using `mockGreeter.mock.greet       .withArgs("argument")       .returns("mockedReturn 2")`

```solidity
//SPDX-License-Identifier: MIT
pragma solidity ^0.6.8;

import "./Greeter.sol";

contract GreetManager {
    Greeter greeter;

    constructor(Greeter greeter_) public {
        greeter = greeter_;
    }

    function useGreeter(string memory custom)
        public
        view
        returns (string memory)
    {
        return greeter.greet(custom);
    }
}
```

```javascript
const { expect } = require("chai");
const {
  deployContract,
  deployMockContract,
  MockProvider,
} = require("ethereum-waffle");
const Greeter = require("../artifacts/Greeter.json");
const GreetManager = require("../artifacts/GreetManager.json");

const wallet = new MockProvider().getWallets()[0];

describe("Greeter", () => {
  it("Should return what is set by mock", async () => {
    const mockGreeter = await deployMockContract(wallet, Greeter.abi);
    const greetManager = await deployContract(wallet, GreetManager, [
      mockGreeter.address,
    ]);

    await mockGreeter.mock.greet.returns("Hello new world!");

    await mockGreeter.mock.greet
      .withArgs("Hello old world!")
      .returns("Hello new (not old) world!");

    expect(await greetManager.useGreeter("custom")).to.equal(
      "Hello new world!"
    );
    expect(await greetManager.useGreeter("Hello old world!")).to.equal(
      "Hello new (not old) world!"
    );
  });
});
```

### 2.3 ENS (Ethereum Name Service)

[ENS](<https://ens.domains/>) is nowadays quite often supported. You can register domains and subdomains in it. Many projects like MetaMask support this, so you can use simple names instead of addresses. If you are using it in your contracts, you can easily deploy the complete ENS smart contracts system with Waffle.

```javascript
const provider = new MockProvider();
await provider.setupENS();
const ens = provider.ens;
```

This is all you need for the setup. Now you can create domains and subdomains:

```javascript
await ens.createTopLevelDomain('com');
await ens.createSubDomain('soliditydeveloper.com');
await ens.createSubDomain('vitalik.buterin.eth', {recursive: true}); // all in one
```

And you can set addresses via:

```javascript
// name to address
await ens.setAddress('soliditydeveloper.com', '0x001...03');

// address to name
await ens.setAddressWithReverse('soliditydeveloper.com', '0x001...03');
```

## Waffle or Truffle?

I think both are viable options these days. Both have unique features. In particular I like the Waffle fixtures as they can speed up test runs significantly. If you feel like that's something you don't really need, Truffle might be the better option just due to more active developments, more tooling and support.

You should also consider the size of the project and how long it will be maintained in the future. It's certainly an option to use only Waffle. For example Uniswap v2 core being a smaller project exists purely with Waffle: [https://github.com/Uniswap/uniswap-v2-core](<https://github.com/Uniswap/uniswap-v2-core>).

Or once again, why choose? Just use Waffle and Buidler for testing and Truffle for deployments / tool support.

Truffle Waffles: Yum!

![Truffle-Waffle](<https://cdn0.scrvt.com/b095ee27d37b3d7b6b150adba9ac6ec8/369b051e30fd82ba/ca414b21fd8b/v/42161bb32a82/truffle-waffle.jpg>)
