Skip to content

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.

  1. The shared Chai setup and
  2. 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.


Last update: April 29, 2022