Unit Testing in Truffle

Hey @Jun_0113

For async function -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

If the concept is still not clear just google it, you will find tons of info and example, also I suggest you to write simple code and practice these concepts a lot, as they are really important for a developer.

instance = await People.new()
instance = await People.deployed()

They are kind of the same thing, both deploy a new instance of the contract.
.deploy() you can access the deployed instance with .deployed() .
You typically use .new() in tests to avoid the possibility of any side-effects.

Happy coding,
Dani

1 Like

Thanks Dani. I suppose it would be more secure if we added validation to prevent the user from entering 0 into the createperson function. Cheers for the response. Have a great day in crypto!

1 Like

Hi Dani,
Thank you for you response. I will read the article and practice async function.
I have a follow up question on the other one.

So if these two new and deployed are the same, is it ok to replace the one with .new? This is the code from the test in people project.

before(async function(){
instance = await People.deployed()
});

Thank you!

1 Like

The below are my two tests:

it("shouldn't allow non-owner to delete person", async function(){

    let instance = await People.deployed();

    await truffleAssert.fails(instance.deletePerson(accounts[0], {from: accounts[1]}), truffleAssert.ErrorType.REVERT);

    let result = await instance.getPerson();

    console.log("age of queried person:", result.age.toNumber());

    assert(result.age.toNumber() !== 0, "Person deleted by non-owner");
  });

  it("shouldn't allow only owner to delete person", async function(){
    let instance = await People.deployed();

    await instance.deletePerson(accounts[0]);

    let result = await instance.getPerson();

    console.log("age of queried person:", result.age.toNumber());

    assert(result.age.toNumber() === 0, "Person not deleted by owner");
  });

In the first test, I don’t think it’s necessary to check the age of the person, that it is still 65 (after the person was created/updated in the previous tests). But I thought I should leave the 3 lines there, as an alternative method to verify that “Bob” was indeed not deleted by another account.

1 Like

The first deletePerson call will not work correctly and therefore pass the truffleAssert.fails test:

it("should not allow others to delete people", async function(){
  let instance = await People.deployed();
  let creator = await instance.getCreator(0);
  await truffleAssert.fails(instance.deletePerson(creator, {from: accounts[1]}),
  truffleAssert.ErrorType.REVERT);
  });

This second call will not pass the truffleAssert.fails (meaning the deletePerson function will work correctly):

  it("should allow the owner to delete people", async function(){
    let instance = await People.deployed();
    let creator = await instance.getCreator(0);
    await truffleAssert.passes(instance.deletePerson(creator, {from: accounts[0]}),
    truffleAssert.ErrorType.REVERT);
  });

(I decided to use get the account info from the contract assuming I don’t know who made it, but also assuming I know the index of the person I want to remove.)

This was a good challenge, really made me think. :thinking:

UPDATE–> after seeing @filip’s answer I then changed the second truffleAssert.fails to truffleAssert.passes
Nice one Filip :+1:

Here are the tests for the last assignment:

  it("should have a balance increase after createPerson", async function(){
    instance = await People.new();
    let onChainBalanceBefore = await web3.eth.getBalance(instance.address);
    await instance.createPerson("Lisa", 65, 177, {value: web3.utils.toWei("1", "ether"), from: accounts[3]});
    let onChainBalanceAfter = await web3.eth.getBalance(instance.address);
    let balanceVarAfter = await instance.contractBalance();
    assert(balanceVarAfter == onChainBalanceAfter && onChainBalanceAfter > onChainBalanceBefore, "The balance did not increase correctly.");
  });
  it("should have a zero balance after increasing ownerBalance", async function(){
    let ownerBalance = await web3.eth.getBalance(accounts[0]);
    await truffleAssert.passes(instance.withdrawAll({from: accounts[0]}),"The withdrawAll function not called by owner");
    let onChainBalance = await web3.eth.getBalance(instance.address);
    let newOwnerBalance = await web3.eth.getBalance(accounts[0]);
    let variableBalance = await instance.contractBalance();
    assert(variableBalance == 0 && onChainBalance == 0 && newOwnerBalance > ownerBalance, "The balance wasn't withdrawn.");
  });
1 Like

Here is my solution to the assignment.

const People = artifacts.require("People");
const truffleAssert = require("truffle-assertions");

contract("People", async function(accounts){

  it("Should delete person only by owner", async ()=>{
    let instance = await People.deployed();
    await instance.createPerson("Bob", 65, 190, {value: web3.utils.toWei("1", "ether"), from: accounts[1]});
    await truffleAssert.fails(instance.deletePerson(accounts[0], {from: accounts[1]}),
    truffleAssert.ErrorType.REVERT);
    await truffleAssert.passes(instance.deletePerson(accounts[1], {from: accounts[0]}),
    truffleAssert.ErrorType.REVERT);
  });
  it("Should withdraw from owner only", async ()=>{
    let instance = await People.deployed();
    await truffleAssert.fails(instance.withdrawAll({from: accounts[1]}), truffleAssert.ErrorType.REVERT);
    await truffleAssert.passes(instance.withdrawAll({from: accounts[0]}), truffleAssert.ErrorType.REVERT);
  });
});

1 Like

//Test for only owner can delete people
const People = artifacts.require(“People”);
const truffleAssert = require(“truffle-assertions”);

contract(“People”, async function(accounts){
// bring variable out of each it
let instance;

before(async function(){
	instance = await People.deployed()
});

// People contract was modified to require payment of 100 wei
it(“shouldn’t create a person with age over 150”, async function (){
await truffleAssert.fails(instance.createPerson(“Bob”, 200, 190, {value: 100}), truffleAssert.ErrorType.REVERT);
});
it(“shouldn’t create a person without adequate payment”, async function(){
await truffleAssert.fails(instance.createPerson(“Alice”, 50, 190, {value: 99}), truffleAssert.ErrorType.REVERT);
});
it(“should allow owner to delete Person”, async function(){
await instance.createPerson(“Bob”, 50, 190, {value: 100});
await truffleAssert.passes(instance.deletePerson(accounts[1]), truffleAssert.ErrorType.REVERT);
});
it(“shouldn’t allow non-owner to delete Person”, async function(){
await instance.createPerson(“Bob”, 50, 190, {value: 100});
await truffleAssert.fails(instance.deletePerson(accounts[1], {from: accounts[2]}), truffleAssert.ErrorType.REVERT);
});
// testing that specifying accounts[0] (owner) can execute onlyOwner
it(“should allow specified owner to delete Person”, async function(){
await instance.createPerson(“Bob”, 50, 190, {value: 100});
await truffleAssert.passes(instance.deletePerson(accounts[1], {from: accounts[0]}), truffleAssert.ErrorType.REVERT);
});

});

1 Like

My solution for the Vale Assignment:

const People = artifacts.require("People");
const truffleAssert = require("truffle-assertions");

contract(People, async (accounts)=> {

  let instance;
  let initialContractBalance;
  let initialOnChainContractBalance;
  let currentContractBalance;
  let currentOnChainBalance;

  before(async ()=> {
    instance = await People.deployed();
    initialContractBalance = await instance.balance();
    initialOnChainContractBalance = await web3.eth.getBalance(instance.address);
    // console.log("Initial Contract balance is: " + initialOnChainContractBalance);
  })

  afterEach(async ()=>{
    currentContractBalance = await instance.balance();
    currentOnChainBalance = await web3.eth.getBalance(instance.address);
  })

  it("should be no balance in contract var and onchain", async ()=>{
    assert(parseInt(initialContractBalance) === parseInt(initialOnChainContractBalance) && parseInt(initialContractBalance) === 0 && parseInt(initialOnChainContractBalance) === 0,
    "balance is not the same on chain as the public balance variable");
  });
  it("should update the balance and the balance is the same in var and on chain", async ()=> {
    instance.createPerson("James", 24, 177, {value: web3.utils.toWei("1", "ether"), from: accounts[1]});
    currentContractBalance = await instance.balance();
    currentOnChainBalance = await web3.eth.getBalance(instance.address);
    assert(parseInt(currentContractBalance) === parseInt(currentOnChainBalance),
    "balance not the same on chain as the public balance variable after adding value");
  })
  it("is possible for only owner to withdraw", async ()=> {
    await truffleAssert.fails(instance.withdrawAll({from: accounts[1]}), truffleAssert.ErrorType.REVERT);
    await truffleAssert.passes(instance.withdrawAll({from: accounts[0]}));
  })
  it("should be 0 in balance", async ()=>{
    assert(parseInt(currentOnChainBalance) === 0 && parseInt(currentContractBalance) === 0,
    "balance should be 0!");
  })

})

1 Like

These are my tests for the Value Assignment

  1. First test checks if the balance of the contract reflects the same value as the balance retrieved from the blockchain, after creation of new person.
  2. Second test checks that the balance of the contract increments by 1 ether when a person is created
  3. Third test checks that after withdrawal, the contract remains without funds, while the contract of the owner increases as much as it is being withdrawn from the contract
  4. Fourth test checks that the owner is able to withdraw from the contract balance
  5. Fifth test checks that non-owners are not able to withdraw from the contract balance

While tests 3 and 4 might be a bit redundant (test 3 will never pass if test 4 doesn’t pass in the first place), each verifies a specific test scenario and that’s why I thought two separate tests were warranted.

  it("balance of contract reflected by public field must be equal to balance reported by the blockchain, after creation of a new person", async function(){
    await instance.createPerson("Jhon", 31, 180, {value: web3.utils.toWei('1',"ether")});

    let blockchainBalance = parseInt(await web3.eth.getBalance(People.address));
    let contractBalance = parseInt(await instance.balance());

    console.log("balance in contract:", contractBalance);
    console.log("balance on blockchain:", blockchainBalance);

    assert(contractBalance === blockchainBalance, "balances are not equal");
  });

  it("should increase balance of contract when person is created", async function(){
    let initialBalance = parseInt(await instance.balance());

    await instance.createPerson("Jhon", 31, 180, {value: web3.utils.toWei('1',"ether")});

    let finalBalance = parseInt(await instance.balance());

    console.log("initial balance:", initialBalance);
    console.log("final balance:", finalBalance);

    assert(initialBalance === finalBalance - web3.utils.toWei('1',"ether"), "balances are not equal");
  });

  it("withdrawal from contract balance leaves no funds in the contract, while the owner contract blance increases accordingly", async function(){
    let initialContractBalance = parseInt(await instance.balance());
    let initialAccountBalance = parseInt(await web3.eth.getBalance(accounts[0]));

    console.log("initial contract balance:", initialContractBalance);
    console.log("initial account balance:", initialAccountBalance);

    instance.withdrawAll();

    let finalContractBalance = parseInt(await instance.balance());
    let finalAccountBalance = parseInt(await web3.eth.getBalance(accounts[0]));

    console.log("final contract balance:", finalContractBalance);
    console.log("final account balance:", finalAccountBalance);
  });

  it("should allow withdrawal from contract balance only by contract owner", async function(){
    instance = await People.new();

    await truffleAssert.passes(instance.withdrawAll());
  });

  it("should fail withdrawal from contract balance by non-owner of contract", async function(){
      instance = await People.new();

      await truffleAssert.fails(instance.withdrawAll({from: accounts[1]}), truffleAssert.ErrorType.REVERT);
  });
1 Like

Validate the user input is critical when coding a smart contract.
Always do that :slight_smile:

Happy learning,
Dani

Hey @Jun_0113

So if these two new and deployed are the same, is it ok to replace the one with .new? This is the code from the test in people project.
before(async function(){
instance = await People.deployed()
});

This is indeed the correct way to go.
Well done

1 Like

My solution for the owner test assignment:

contract("People", async function(accounts){
    it("should delete a person", async function(){
        let instance = await People.deployed();
        await instance.createPerson("Bob", 50, 190, {value: web3.utils.toWei("1", "ether")});
        await instance.deletePerson(accounts[0]);
        let result = await instance.getPerson();
        assert(result.age.toNumber() === 0, "The person wasn't deleted");
    })
    it("shouldn't delete a person", async function(){
        let instance = await People.deployed();
        await instance.createPerson("Bob", 50, 190, {value: web3.utils.toWei("1", "ether")});
        await truffleAssert.fails(instance.deletePerson(
            accounts[0], {from : accounts[1]}), truffleAssert.ErrorType.REVERT);
    })
    //other tests
})
1 Like

Owner Test Assignment:

    it("shouldn't allow non owner to delete people", async () => {
            let instance = await People.deployed();
            await instance.createPerson('Lisa', 67, 180, {from: accounts[1], value: web3.utils.toWei('1', 'ether')});
            await truffleAssert.fails(
                instance.deletePerson(accounts[1], {from: accounts[1]}), 
                truffleAssert.ErrorType.REVERT
            );
    });
    it("should allow owner to delete people", async () => {
            let instance = await People.deployed(accounts);
            await instance.createPerson('Mike', 67, 180, {from: accounts[1], value: web3.utils.toWei('1', 'ether')});
            await truffleAssert.passes(
                instance.deletePerson(accounts[1], {from: accounts[0]}),
                'only owner should be able to delete'
            );
    });
1 Like

My solution

contract("People", async function ([owner, user1]) {
    let instance;
    before(async function(){
        instance = await People.deployed()
      });

    it("user that is not owner should not be able to remove person", async function () {
        await instance.createPerson("Bob", 65, 190, { value: web3.utils.toWei("1", "ether"), from: user1 });
        await truffleAssert.fails(instance.deletePerson(user1, {from: user1}), truffleAssert.ErrorType.REVERT);
    });
    it("owner should be able to delete user", async function () {
        await instance.createPerson("Bob", 65, 190, { value: web3.utils.toWei("1", "ether"), from: user1 });
        await instance.deletePerson(user1, { from: owner });
        let result = await instance.getPerson({ from: user1 });
        assert(result.name === '', "Person was not removed, name exist");
        assert(result.age.toString() === '0', "Person was not removed, age exist");
        assert(result.height.toString() === '0', "Person was not removed, height exist");
        
    });

})
1 Like
    it("when person is added then balance should be increased", async function () {
        let user_balance_before = await web3.eth.getBalance(user1)
        await instance.createPerson("Bob", 65, 190, { value: web3.utils.toWei("5", "ether"), from: user1 });
        let user_balance_after = await web3.eth.getBalance(user1);
        let contract_balance = await instance.balance();
        assert(user_balance_before > user_balance_after);
        assert(contract_balance.toString() === '5000000000000000000')
    });
    it("owner can withdraw and balance and contract balance is 0 and owner balance is incresed", async function () {
        await instance.createPerson("Bob", 65, 190, { value: web3.utils.toWei("2", "ether"), from: user1 });
        let owner_balance_before = await web3.eth.getBalance(owner);
        await instance.withdrawAll({ from: owner });
        let owner_balance_after = await web3.eth.getBalance(owner);
        assert(owner_balance_after > owner_balance_before);
        let contract_balance = await instance.balance();
        assert(contract_balance.toString() === '0')
    });
1 Like

i am facing issues, migration does not compile and it throws error asking for truffle config.
what shall i do? also changed the version of ganache still not working

1 Like

I wrote five tests for the value assignment.
The first test checks the balance variable increases on createPerson.
The second test checks the blockchain contract balance increases on createPerson.
The third test checks the balance variable and the blockchain contract balance are equal.
The fourth test checks the owner can withdraw eth.
The fifth test checks non-owners can’t withdraw eth.

it(“should increase balance variable on createPerson”, async function(){
let balanceVar = await instance.balance();
await instance.createPerson(“Hog”, 65, 1, {value: web3.utils.toWei(“1”, “ether”), from: accounts[0]});
let balanceVarNew = await instance.balance();
assert(balanceVarNew > balanceVar, “balance variable not increasing on createPerson”);
});
it(“should increase actual contract balance on createPerson”, async function(){
let actualBalanceBefore = await web3.eth.getBalance(instance.address);
await instance.createPerson(“Hog”, 65, 1, {value: web3.utils.toWei(“1”, “ether”), from: accounts[0]});
let actualBalanceAfter = await web3.eth.getBalance(instance.address);
assert(actualBalanceAfter > actualBalanceBefore, “actual balance not increasing on createPerson”);
});
it(“should have equal value for balance variable and actual contract balance”, async function(){
await instance.createPerson(“Hog”, 65, 1, {value: web3.utils.toWei(“1”, “ether”), from: accounts[0]});
let balanceVar = parseFloat(await instance.balance());
let actualBalance = parseFloat(await web3.eth.getBalance(instance.address));
assert(balanceVar === actualBalance, “balance variable not matching contract balance”);
});
it(“should allow owner to withdraw eth”, async function(){
let ownerBalance = await web3.eth.getBalance(accounts[0]);
await instance.withdrawAll({from: accounts[0]});
let contractBalanceAfter = parseFloat(await web3.eth.getBalance(instance.address));
let ownerBalanceAfter = await web3.eth.getBalance(accounts[0]);
assert(ownerBalanceAfter > ownerBalance && contractBalanceAfter === 0, “owner not recieving funds from withdrawAll”);
});
it(“shouldn’t allow non-owner to withdraw eth”, async function(){
await truffleAssert.fails(instance.withdrawAll({from: accounts[1]}), truffleAssert.ErrorType.REVERT);
});

1 Like

Hey @ghiasi12reza

Can you show a screenshot of the error and provide the config file?
Also in which folder are you trying to migrate the projct?

cheers,
Dani

1 Like

let instance;
    beforeEach(async function() {
        instance = await People.new();
        await instance.createPerson('Josh', 45, 180, {from: accounts[1], value: web3.utils.toWei('1', 'ether')});
    });

.
.
.

it("should increase contract balance by 1 eth after creating a person", async () => {
        let contractBalance = parseFloat(await instance.balance());
        assert(contractBalance == web3.utils.toWei('1', 'ether'));
    });
    it("should have same balance on contract as on blockchain after creating person", async () => {
        let contractBalance = parseFloat(await instance.balance());
        let actualBalance = await web3.eth.getBalance(instance.address);
        assert(contractBalance == actualBalance);
    });
    it("shouldn't allow non owner to withdraw funds from contract", async () => {
        await truffleAssert.fails(instance.withdrawAll({from: accounts[1]}), truffleAssert.ErrorType.REVERT);
    });
    it("should allow owner to withdraw funds from contract", async () => {
        await truffleAssert.passes(instance.withdrawAll({from: accounts[0]}), truffleAssert.ErrorType.REVERT);
    })
    it("should increase owner balance after withdrawing from contract", async () => {
        let initialBalance = await web3.eth.getBalance(accounts[0]);
        await instance.withdrawAll({from: accounts[0]});
        let finalBalance = await web3.eth.getBalance(accounts[0]);
        assert(finalBalance > initialBalance);
    });
    it("should reset contract balance to 0 after withdrawAll()", async () => {
        await instance.withdrawAll({from: accounts[0]});
        assert(await parseFloat(await instance.balance()) === 0);
    });
1 Like