Getting Started

This guide will cover what you need to know to start using Test Environment, from installation of dependencies to configuration.

To learn instead how to migrate an existing Truffle project, check out our Migrating from Truffle guide.

If you’re new to smart contract testing, you should probably first look at our [guide for automated tests], and then come back here.

Installing Dependencies

Unlike Truffle, Test Environment is a testing library: it provides utilities that help you write tests, but it doesn’t run your tests for you.

This is a good thing: it means you are free to use any test runner of your choice. Here we will use Mocha (along with Chai):

$ npm install --save-dev @openzeppelin/test-environment mocha chai
Head to our Choosing a Test Runner guide to learn about different test runners, their pros and cons, and how to use them.

Writing Tests

Each test file should have a require statement importing Test Environment. This will bring up the local blockchain where your tests will run, and provide utilities to interact with it.

const { accounts, contract } = require('@openzeppelin/test-environment');

The exports you will be using the most are accounts and contract.

accounts

This is an array with the addresses of all unlocked accounts in the local blockchain. They are also prefunded, so they can all be used to send transactions.

A good practice is to use array destructuring to give meaningful names to each account, such as:

const [ admin, purchaser, user ] = accounts;

This will let you write clearer tests by making it obvious which actor is executing each action:

const crowdsale = await Crowdsale.new({ from: admin });

await crowdsale.buyTokens(amount, { from: purchaser });
Unlike Truffle’s accounts array, the default address (the one that is used when you don’t provide one) is not included in accounts. This will help you avoid common pitfalls while writing tests. Head to the API reference to learn more.

contract

You will use this function to load your compiled contracts into JavaScript objects. By default these will be Truffle contracts, but they can be configured to be web3 contracts.

// Loads the built artifact from build/contracts/Crowdsale.json
const Crowdsale = contracts.fromArtifact('Crowdsale');

Test Cases

The overall structure of your tests will be determined not by Test Environment, but by your test runner of choice.

In Mocha, test cases are declared with the it function, and grouped in describe blocks.

We’ll use the following contract to provide an example. It consists of a simple access control mechanism via OpenZeppelin Contracts' Ownable:

// contracts/MyContract.sol

pragma solidity ^0.5.0;

import "@openzeppelin/contracts/ownership/Ownable.sol";

contract MyContract is Ownable { }

And here’s how testing this contract using Test Environment and Mocha looks like:

// test/MyContract.test.js

const { accounts, contract } = require('@openzeppelin/test-environment');

const { expect } = require('chai');

const MyContract = contract.fromArtifact('MyContract');

describe('MyContract', function () {
  const [ owner ] = accounts;

  beforeEach(async function {
    this.myContract = await MyContract.new({ from: owner });
  });

  it('the deployer is the owner', async function () {
    expect(await this.myContract.owner()).to.equal(owner);
  });
});

In Choosing a Test Runner you can compare how this example looks in each test runner.

Running your Tests

Test Environment is not executable: tests are run by invoking your runner of choice directly. We recommend that you do this by adding a test script to your package.json.

This is what that looks like when using Mocha:

// package.json

"scripts": {
  "test": "mocha --exit --recursive"
}
Mocha’s --exit flag is required when using Truffle contracts. Otherwise, the test suite will not exit. Learn more.

All set! Running npm test to execute the test suite:

$ npm test

  MyContract
    ✓ the deployer is the owner

Because we’re running Mocha directly, it is very easy to pass additional options to it. Try the following, which will cause Mocha to stop immediately on the first failing test:

$ npm test -- --bail

Compiling your Contracts

Test Environment requires your contracts to be compiled: you can use the OpenZeppelin CLI to do this.

$ npm install --save-dev @openzeppelin/cli
$ npx oz compile

Compilation artifacts will be stored in the build/contracts directory, where Test Environment (and most other tools) will read them from.

You can set your project to recompile all contracts when running tests by adding this step to your test script:

// package.json

"scripts": {
  "test": "oz compile && mocha --exit --recursive"
}

Using OpenZeppelin Test Helpers

Complex assertions, such as testing for reverts or events being emitted, can be performed by using the OpenZeppelin Test Helpers.

When used alongside Test Environment, there is no need for manual configuration: require the helpers and use them as usual.

Configuration

Multiple aspects of Test Environment can be configured. The default values are very sensible and should work fine for most testing setups, but you are free to modify these.

To do this, create a file named test-environment.config.js at the root level of your project: its contents will be automatically loaded.

// test-environment.config.js

module.exports = {
  accounts: {
    amount: 10, // Number of unlocked accounts
    ether: 100, // Initial balance of unlocked accounts (in ether)
  },

  contracts: {
    type: 'truffle', // Contract abstraction to use: 'truffle' for @truffle/contract or 'web3' for web3-eth-contract
    defaultGas: 6e6, // Maximum gas for contract calls (when unspecified)

    // Options available since v0.1.2
    defaultGasPrice: 20e9, // Gas price for contract calls (when unspecified)
    artifactsDir: 'build/contracts', // Directory where contract artifacts are stored
  },

  node: { // Options passed directly to Ganache client
    gasLimit: 8e6, // Maximum gas per block
    gasPrice: 20e9 // Sets the default gas price for transactions if not otherwise specified.
  },
};

Advanced Options

These settings are meant to support more complex use cases: most applications will not require using them.

setupProvider

async function setupProvider(baseProvider)

Returns a new web3 provider that will be used by all contracts and helpers.

Often used to wrap the base provider in one that performs additional tasks, such as logging or Gas Station Network integration:

// test-environment.config.js

module.exports = {
  setupProvider: (baseProvider) => {
    const { GSNDevProvider } = require('@openzeppelin/gsn-provider');
    const { accounts } = require('@openzeppelin/test-environment');

    return new GSNDevProvider(baseProvider, {
      txfee: 70,
      useGSN: false,
      ownerAddress: accounts[8],
      relayerAddress: accounts[9],
    });
  },
};

fork and unlocked_accounts

These options allow Test Environment’s local blockchain to fork an existing one instead of starting from an empty state. By using them you can test how your code interacts with live third party protocols (like the MakerDAO system) without having to deploy them yourself!

In forked mode, you will also be able to send transactions from any of the unlocked_accounts (even if you don’t know their private keys!).

// test-environment.config.js

module.exports = {
  node: { // Options passed directly to Ganache client
    fork: 'https://mainnet.infura.io/v3/{token}@{blocknumber}, // An url to Ethereum node to use as a source for a fork
    unlocked_accounts: ['0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'], // Array of addresses specifying which accounts should be unlocked.
  },
};

allowUnlimitedContractSize

Allows unlimited contract sizes. By enabling this flag, the check within the EVM for contract size limit of 24kB (see EIP-170) is bypassed. Useful when testing unoptimized contracts that wouldn’t be otherwise deployable.

Enabling this flag causes Ganache to behave differently from production environments.
// test-environment.config.js

module.exports = {
  node: { // Options passed directly to Ganache client
     allowUnlimitedContractSize: true, // Allows unlimited contract sizes.
  },
};