Crowdsale Unit-Test¶
Now, let's test our Crowdsale Token. Create a new file in /tests/MyTokenSale.test.js:
const Token = artifacts.require("MyToken");
const TokenSale = artifacts.require("MyTokenSale");
var chai = require("chai");
const expect = chai.expect;
const BN = web3.utils.BN;
const chaiBN = require('chai-bn')(BN);
chai.use(chaiBN);
var chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
contract("TokenSale", async function(accounts) {
const [ initialHolder, recipient, anotherAccount ] = accounts;
it("there shouldnt be any coins in my account", async () => {
let instance = await Token.deployed();
expect(instance.balanceOf.call(initialHolder)).to.eventually.be.a.bignumber.equal(new BN(0));
});
});
If you run this, it will give you an error:
Problem is: this won't work out of the box for two reasons.
- The shared Chai setup and
- The missing return statements in the previous smart contract.
General Setup for Chai and Chai-as-Promised¶
A note on the Videos and truffle-assertions
In the videos I am mentioning that you need to return the expect()...
. An attentive student asked where to find more about this, as it seems to be undocumented.
This is where I believe it comes from: If you look at Chai-As-Promised then "should.eventually.be" will return a promise, which means the testing framework needs to be informed about a pending promise. This is the actual example on their website: return doSomethingAsync().should.eventually.equal("foo");
. Having said that, I am not 100% convinced that it's necessary (anymore) since it also works without the return in most cases.
In the meantime I found another wrapper which I can wholeheartedly recommend: Truffle-Assertions. So, as an alternative (or in addition), check out https://github.com/rkalis/truffle-assertions, they are easy to use and cover pretty much anything you will probably come across to test for in Solidity.
Create a new file in tests/chaisetup.js with the following content:
"use strict";
var chai = require("chai");
const expect = chai.expect;
const BN = web3.utils.BN;
const chaiBN = require('chai-bn')(BN);
chai.use(chaiBN);
var chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
module.exports = chai;
Then update the tests/Token.test.js file. Mind the "return" keywords:
const Token = artifacts.require("MyToken");
const chai = require("./chaisetup.js");
const BN = web3.utils.BN;
const expect = chai.expect;
require('dotenv').config({path: '../.env'});
contract("Token Test", function(accounts) {
// rest of the code...
it("All tokens should be in my account", async () => {
// rest of the code...
return expect(instance.balanceOf(initialHolder)).to.eventually.be.a.bignumber.equal(totalSupply);
});
it("I can send tokens from Account 1 to Account 2", async () => {
// rest of the code...
return expect(instance.balanceOf(recipient)).to.eventually.be.a.bignumber.equal(new BN(sendTokens));
});
it("It's not possible to send more tokens than account 1 has", async () => {
return expect(instance.balanceOf(initialHolder)).to.eventually.be.a.bignumber.equal(balanceOfAccount);
});
});
And then fix the TokenSale.test.js:
const Token = artifacts.require("MyToken");
const TokenSale = artifacts.require("MyTokenSale");
const chai = require("./chaisetup.js");
const BN = web3.utils.BN;
const expect = chai.expect;
contract("TokenSale", async function(accounts) {
const [ initialHolder, recipient, anotherAccount ] = accounts;
it("there shouldnt be any coins in my account", async () => {
let instance = await Token.deployed();
return expect(instance.balanceOf.call(initialHolder)).to.eventually.be.a.bignumber.equal(new BN(0));
});
});
Run the tests:
Add more Unit-Tests for actually purchasing a Token¶
In the tests/TokenSale.test.js add the following:
//other code in test
it("all coins should be in the tokensale smart contract", async () => {
let instance = await Token.deployed();
let balance = await instance.balanceOf.call(TokenSale.address);
let totalSupply = await instance.totalSupply.call();
return expect(balance).to.be.a.bignumber.equal(totalSupply);
});
it("should be possible to buy one token by simply sending ether to the smart contract", async () => {
let tokenInstance = await Token.deployed();
let tokenSaleInstance = await TokenSale.deployed();
let balanceBeforeAccount = await tokenInstance.balanceOf.call(recipient);
await expect(tokenSaleInstance.sendTransaction({from: recipient, value: web3.utils.toWei("1", "wei")})).to.be.fulfilled;
return expect(balanceBeforeAccount + 1).to.be.bignumber.equal(await tokenInstance.balanceOf.call(recipient));
});
Run the tests and it should work:
Errors?
If you are running into troubles, unexpected errors, try to restart Ganache!
In the next step we model some sort of Know-Your-Customer Whitelisting Smart Contract. This can be a mockup for a larger KYC solution. But in our case, it will just whitelist addresses by the admin of the system.