Hi all! I think it’s almost there, what do you think? @nventuro @frangio @abcoathup @martriay.
- I can add more about
web3.js
, it()
's and examples.
- I was thinking if I should PR in the docs, right?.
How OpenZeppelin Tests its Smart Contracts.
Install
git clone https://github.com/OpenZeppelin/openzeppelin-solidity.git
cd openzeppelin-solidity
npm install
Usage
To run all the tests:
npm test
To test a particular contract
, describe
, context
or it
, add a .only
after them and npm test
will only run the test for that file.
contract.only('Ownable', function ([_, owner, ...otherAccounts]) {...}
The next should display in the console:
Contract: Ownable
as an ownable
✓ should have an owner (79ms)
✓ changes owner after transfer (475ms)
✓ should prevent non-owners from transferring (166ms)
✓ should guard ownership against stuck state (162ms)
✓ loses owner after renouncement (338ms)
✓ should prevent non-owners from renouncement (157ms)
6 passing (3s)
Reference
OpenZeppelin Tests its Smart Contracts using Truffle 5, Web3 1.0, Mocha, Chai and BN.
Helpers
Import all required modules from openzeppelin-test-helpers.
const { expectRevert, constants } = require('openzeppelin-test-helpers');
const { ZERO_ADDRESS } = constants;
Migrations
Import the contract artifacts (a contract abstraction), in each test file.
const Ownable = artifacts.require('OwnableMock');
This allows to deploy a new instance of the contract using new
:
this.ownable = await Ownable.new({ from: owner });
The truffle migrate
command is not used.
Mocks
OwnableMock.sol is a contract that imports and inherits Ownable.sol.
The mock contracts are a collection of abstract contracts that can be used as the foundation for your own custom implementations. These mocks demonstrate how OpenZeppelin’s secure base contracts can be used with multiple inheritance. They are primarily used for tests, but also serve as good examples for usage and combinations.
Behavior
Group of tests, modular and reusable.
const { shouldBehaveLikeOwnable } = require('./Ownable.behavior');
BDD (Behavior Driven Development)
OpenZeppelin uses Mocha’s BDD Interface to test it’s Smart Contracts.
Async / Await
Async / Await is a way to write asynchronous code.
- Async ensures that the function returns a promise.
- Await makes JavaScript wait until that promise is resolved (fulfilled or rejected) and returns its result.
- When returned, the value of the await expression is that of the fulfilled Promise.
beforeEach() contract()
Before each contract()
function is run, the contracts are redeployed to ganache-cli so the tests within it run with a clean contract state, like explained in the Truffle docs.
The contract()
function contains all the tests.
contract('Ownable', function ([_, owner, ...otherAccounts]) {
beforeEach(async function () {
this.ownable = await Ownable.new({ from: owner });
});
shouldBehaveLikeOwnable(owner, otherAccounts);
});
describe()
Commonly known as test suites, which contain test cases it()
's. They are merely used for grouping “big features” of the contract, and you can have groups within groups. context()
is just an alias for describe()
.
describe('as an ownable', function () {
it('should have an owner', async function () {
(await this.ownable.owner()).should.equal(owner);
});
});
it()
it()
's are usually known as test cases and they are used to test for each way an argument can be wrong.
-
Top level it()
tests the ways the constructor()
can revert, like the example above.
-
Other it()
's to test all the functionalities of the contract and make sure to cover the happy paths, simple view functions and edge cases.
it('changes owner after transfer', async function () {
(await this.ownable.isOwner({ from: other })).should.be.equal(false);
const { logs } = await this.ownable.transferOwnership(other, { from: owner });
expectEvent.inLogs(logs, 'OwnershipTransferred');
(await this.ownable.owner()).should.equal(other);
(await this.ownable.isOwner({ from: other })).should.be.equal(true);
});