Unit Testing in Truffle

Took a while to get these to pass and fail for the correct reasons. Really need to make sure the functions are all called from the correct address using the from attribute. Added an additional test for the positive delete case.

    it('should REVERT when deleting a person if the sender is NOT the contract owner', async function () {
        let instance = await People.deployed();
        await instance.createPerson('Sally', 45, 130, { value: web3.utils.toWei('1', 'ether'), from: accounts[1] });
        let p = await instance.getPerson({from: accounts[1]});            
        assert(p.name === 'Sally', 'precondition: person not created');

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

        let person = await instance.getPerson({from: accounts[1]});        
        assert(person.name === 'Sally', 'person should not have been deleted');
    });

    it('should remove the person from the mapping when it is deleted', async function() {
        let instance = await People.deployed();

        await instance.createPerson('Joe', 40, 180, { value: web3.utils.toWei('1', 'ether'), from: accounts[2] });
        let person = await instance.getPerson({from: accounts[2]});
        assert(person.name === 'Joe', 'precondition: person not created');

        await instance.deletePerson(accounts[2]);
        let result = await instance.getPerson({from: accounts[2]});
        assert(result.name === '', 'person NOT deleted from mapping');
    });
1 Like

@filip regarding the “Truffle Instances” video, wouldn’t it always be better to start with a clean instance using new() in beforeEach()? Starting with a clean and predictable state for every test is generally good practice. If you re-use state between tests it can cause unpredictable errors that are difficult to debug- especially if tests are run in parallel or random order, pre-existing state cannot (and should not) be relied upon.

1 Like

Here are the tests related to balance in createPerson() and withdrawAll(). Took me a while to realize 1) the numbers outputted are “Big Number” instances and 2) I had trouble getting the contract balances to match in the assert statement because I was querying the owner address instead of the deployed contract address (oops!).

let amount;
        let ownerAddress;
        let deployedAddress;
        beforeEach(async function () {
            amount = web3.utils.toWei('1', 'ether');
            ownerAddress = accounts[0];
            deployedAddress = instance.address;
            instance = await People.new();
        });

        async function createPerson(sourceAddr) {
            const initBalance = await instance.balance.call();
            //console.log(initBalance);
            assert.equal(initBalance, 0, 'precondition: balance should be zero');
            await instance.createPerson(
                'Joe', 40, 180, { value: amount, from: sourceAddr });
        }

        it('should increase the balance in both the contract and variable when adding a person', async function () {
            await createPerson(ownerAddress);

            const varBalance = await instance.balance.call();
            assert.equal(varBalance.toString(10), amount.toString(), 'amount not added to balance var');
            const actualBalance = await web3.eth.getBalance(deployedAddress);
            assert.equal(actualBalance.toString(10), amount.toString(), 'contract balance incorrect');
        });

        it('should decrease the balance to zero in both the contract and variable when the owner withdraws all funds', async function () {
            await createPerson(ownerAddress);
            await truffleAssert.passes(instance.withdrawAll());

            const varBalance = await instance.balance.call();
            const actualBalance = await web3.eth.getBalance(deployedAddress);
            assert.equal(varBalance.toString(10), '0', 'variable balance is not zero');
            assert.equal(actualBalance.toString(10), '0', 'contract balance is not zero');
        });

        it('should REVERT when someone other than the contract owner attempts to withdraw funds', async function () {
            await truffleAssert.fails(
                instance.withdrawAll({ from: accounts[1] }),
                truffleAssert.ErrorType.REVERT
            );
        });
    });

EDIT: I noticed 2 things:

  1. instance = await People.new(); should be above deployedAddress (this was correct in my solution but I wrapped the news tests in a describe blow and combined the 2 beforeEach blocks wrong when pasting)

  2. I forgot the the test case for checking the owner balance increased. Which I did after seeing the solution video.

@filip when I attempted to test that the amount transferred to the owner on withdrawAll() was equal to 1 ETH the on chain increase in value was slightly less than 1 ETH, I’m assuming this is because of the gas cost of the transaction? This was probably why your solution just used > instead of equality, yes?

1 Like

Yes that’s actually a good idea. Something that I’ve been thinking about as well. In the end, my tests look a little bit sloppy. So I totally agree with you. That would be way easier to understand and would provide clarity.

Great job!! I’m proud that you stumbled upon all the challenges and solved them. Makes me so happy!

Regarding the 1 ETH question, you are correct. There are gas fees deducted.

I didnt post anything…, I was confused about the concept of owner and creator and thus was having “strange results”. Then…on your solution… Oh right! the owner of the contract not the creator of the person… f… Will be better nex time

1 Like

The Owner test assignment:
I tried to do it in one test as follows, using “for” . at first I checked the test for all accounts, but there were not enough ETH, so I limited the loop to accounts [0],[1] and [2]…:

 it("Only Owner can delete a Person ", async function(){
    let instance = await People.deployed();
    for (i = 0; i <3; i++) {
    await instance.createPerson("Bob", 65, 155, {from: accounts[i],value: web3.utils.toWei("1", "ether")});
    await truffleAssert.passes(instance.deletePerson(accounts[i], {from: accounts[0]}), truffleAssert.ErrorType.REVERT);
    await instance.createPerson("jhon", 65, 155, {from: accounts[i],value: web3.utils.toWei("1", "ether")});
    await truffleAssert.fails(instance.deletePerson(accounts[i], {from: accounts[i+1]}), truffleAssert.ErrorType.REVERT);
    };
1 Like

Hi @Golden_Pig_Coin
I saw that you solved your issue bellow but just to let you know that the initial error was a typo.

assert(result.age.toNumber === 65, "Age not set correctly");
Should be:
assert(result.age.toNumber() === 65, "Age not set correctly");
toNumber is a function you need to add parentheses

1 Like

Here is my tests solution for the assignement:

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

contract("People", async function(accounts){
	it("Should set senior status correctly", async ()=>{
		let instance = await People.deployed();
		await instance.createPerson("Bob", 65, 190, {value: web3.utils.toWei("1", "ether"), from: accounts[0]});
		let result = await instance.getPerson();
		assert(result.senior === true, "Senior level not set");
	});
	it("Shouldn't be delete by an account which is not the owner of the contract", async ()=>{
		let instance = await People.deployed();
		await truffleAssert.fails(instance.deletePerson(accounts[0], {from: accounts[1]}),
									truffleAssert.ErrorType.REVERT);
	});
	it("Should be able to delete your own account", async ()=>{
		let instance = await People.deployed();
		let person = await instance.getPerson();
		assert(person.age.toNumber() !== 0 && person.name !== '',
				"A person should exist and have age and name set before this test is executed");
		await instance.deletePerson(accounts[0], {from: accounts[0]});
		person = await instance.getPerson();
		assert(person.age.toNumber() === 0 && person.name === '',
				"Delete person didn't have erase the person correctly");
	});
});
1 Like

Hi Filip /everyone

I don’t understand the difference between “assert” and “truffleAssert.passes”?

Thanks :slight_smile:
Guy

1 Like

My answer for Value Assignment:

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

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

	let instance;

	beforeEach(async ()=> {
		instance = await People.new();
	});

	it("Should have a correct balance", async ()=>{
		let old_balance =  await web3.eth.getBalance(instance.address);
		await truffleAssert.passes(instance.createPerson("Bob", 65, 190, {value: web3.utils.toWei("1.2", "ether"), from: accounts[0]}),
									"createPerson method should have minimun ether");
		let new_balance =  await web3.eth.getBalance(instance.address);
		await truffleAssert.passes((new_balance > web3.utils.fromWei(old_balance, "ether"), "New balance should be greater when a new person is added"));
		let var_balance = await instance.balance();
		await truffleAssert.passes((parseFloat(var_balance) > new_balance,
			"New balance should be greater when a new person is added"));
	});

	it("Should be able to withdraw", async ()=>{
		let old_balance =  await web3.eth.getBalance(instance.address);
		await truffleAssert.passes(instance.createPerson("Bob", 65, 190, {value: web3.utils.toWei("1.2", "ether"), from: accounts[0]}),
									"createPerson method should have minimun ether");
		let new_balance =  await web3.eth.getBalance(instance.address);
		await truffleAssert.passes(new_balance > old_balance, "New balance should be greater when a new person is added");
		await truffleAssert.passes(instance.withdrawAll({from: accounts[0]}),
								"Contract owner should be able to withdraw");
		await truffleAssert.passes(new_balance === old_balance , "new balance should be equal to 0 when fund a retrieved");
	});

	it("Shouldn't be able to withdraw", async ()=>{
		let old_balance =  await web3.eth.getBalance(instance.address);
		await truffleAssert.passes(instance.createPerson("Bob", 65, 190, {value: web3.utils.toWei("1.2", "ether"), from: accounts[0]}),
									"createPerson method should have minimun ether");
		let new_balance =  await web3.eth.getBalance(instance.address);
		await truffleAssert.passes(new_balance > old_balance, "ether", "New balance should be greater when a new person is added");
		await truffleAssert.fails(instance.withdrawAll({from: accounts[1]}),
								  truffleAssert.ErrorType.REVERT);
		await truffleAssert.passes(new_balance > old_balance , "new balance should be equal when withdraw fail");
	});
});

Edit: I didn’t get in the explanation sthat we had to check the balance variable from the contract :slight_smile:

1 Like

Hi @Guy
I think it’s just a naming convention it does the same thing, but when you are reading all your tests it is more clear that the assertions should pass.

You will have to read the detail of the assert.
assert(1 > 0) passes
assert(0 > 1) fails
Yeah it look simple with 1 and 0 but with a complicated assertion you will have to read the detail.
With a syntactic coloration set in your editor you will see passes faster.

There is also the truffleAssert.reverts which is nice.It lets you know that the test should revert.
The assertions is not just false but it’s catching an error revert from the smart contract. It’s also better then just fails.

Maybe their is other advantages ¯_(ツ)_/¯

1 Like

No problems, so far. Great course. My solution for the Owner Test Assignment:

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

contract("People", async function(accounts){
   it("shouldn't delete person without beeing owner", async function(){
        let instance = await People.deployed();
        await instance.createPerson("Peter", 30, 123, {value: web3.utils.toWei("1","ether")}); // account[0]
        await truffleAssert.fails(instance.deletePerson(accounts[0], {from: accounts[1]}), truffleAssert.ErrorType.REVERT);
    });
    it("should delete persen correctly", async function(){
        let instance = await People.deployed();
        await instance.deletePerson(accounts[0]);
        assert(instance.getPerson().name === "", "Delete failed. Person exists: " + instance.getPerson().name);
    });
});
1 Like

The one is a JavaScript function called assert with a boolean parameter (condition). The function fails if the condition evaluates to false.
assert(<CONDITION>)

The other is a function of the imported truffleAssert object and checks if the given transaction passed without errors.
truffleAssert.passes(<TRANSACTION>)

Hope that helps.

4 Likes

Exactly! Thanks for helping <3

Great team!!!
Thanks

This is what I’ve done

it("the contract has the correct balance", async()=>{
        let instance = await People.new();
        await instance.createPerson("Alice",25,160,{value: web3.utils.toWei("1","ether"), from: accounts[1]});
        let contract_balance = await instance.balance();
        let address_balance = await web3.eth.getBalance(instance.address);
        //console.log(await instance.balance()/1000000000000000000);
        //console.log(await web3.eth.getBalance(instance.address)/1000000000000000000);
        assert(contract_balance == address_balance);
    });

    it("the contract was withdrawn succesfully", async()=>{
       let instance = await People.new();
       await instance.createPerson("Alice",25,160,{value: web3.utils.toWei("1","ether"), from: accounts[3]});
       await instance.withdrawAll();
       let contract_balance = await instance.balance();
       assert(contract_balance==0);
    });

    it("the owner withdraw succesfully", async()=>{
        let instance = await People.new();
        let value_to_send = "1";
        await instance.createPerson("Alice",25,160,{value: web3.utils.toWei(value_to_send,"ether"), from: accounts[3]});
        let owner_balance_before = Math.round(await web3.eth.getBalance(accounts[0])/1000000000000000000*100)/100;
        //console.log(owner_balance_before);
        await instance.withdrawAll();
        let owner_balance_after = Math.round(await web3.eth.getBalance(accounts[0])/1000000000000000000*100)/100;
        //console.log(owner_balance_after);
        assert(owner_balance_before+parseInt(value_to_send)==owner_balance_after);
    });
1 Like

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

contract(“People”, async function(accounts){
it(“shouldn’t be possible to delete a person without being the contract owner”, async function(){
let instance = await People.deployed();
await instance.createPerson(“Ivo”, 30, 210, {value: web3.utils.toWei(“1”,“ether”), from: accounts[1]});
await truffleAssert.fails(instance.deletePerson(accounts[1], {from: accounts[1]}), truffleAssert.ErrorType.REVERT);
});

it("shuld delete pertson if he is the owner", async function(){
     let instance = await People.deployed();
    await truffleAssert.passes(instance.deletePerson(accounts[1], {from: accounts[0]}), truffleAssert.ErrorType.REVERT);

});
it(“shold withdrawAll ballance”, async function(){
let instance = await People.deployed();
await truffleAssert.passes(instance.withdrawAll({from: accounts[0]}), truffleAssert.ErrorType.REVERT);
});
it(“shold not take the ballance”, async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.withdrawAll({from: accounts[1]}), truffleAssert.ErrorType.REVERT);
});
it(“Shold not get creator”, async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.getCreator(accounts[1],{from: accounts[1]}), truffleAssert.ErrorType.REVERT);
});

});

1 Like

const People = artifacts.require(“People”);

const truffleAssert = require(“truffle-assertions”);
contract(“People”, async function(accounts){

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

it(“Shold Increase”, async function(){

await instance.createPerson("Jonnn", 55, 133, {from: accounts[4], value: web3.utils.toWei("1", "ether")});

let startBallace = parseFloat(await web3.eth.getBalance(accounts[0]));
await instance.withdrawAll();
let lastballance = parseFloat(await web3.eth.getBalance(accounts[0]));
assert(startBallace < lastballance, "Ballance withdrawn!");

});

it(“shold withdrawAll ballance”, async function(){
await truffleAssert.passes(instance.withdrawAll({from: accounts[0]}), truffleAssert.ErrorType.REVERT);
});
it(“shold not take the ballance”, async function(){
await truffleAssert.fails(instance.withdrawAll({from: accounts[1]}), truffleAssert.ErrorType.REVERT);
});

});

1 Like

Here is my solution to the Value Assignment:

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

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

  let instance;

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


  it("should only allow owner to withdraw correctly", async function(){
    await instance.createPerson("Bob", 60, 200, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
    // try to withdraw as non-owner
    await truffleAssert.fails(instance.withdrawAll({from: accounts[1]}), truffleAssert.ErrorType.REVERT);

    let obefore = await web3.eth.getBalance(accounts[0]);
    
    // withdraw as owner
    await truffleAssert.passes(instance.withdrawAll({from: accounts[0]}));
    let oafter = await web3.eth.getBalance(accounts[0]);
    assert(oafter > obefore, "Owners balance expected to be > " + obefore + " but is " + oafter);    
    let cafter = parseFloat(await instance.balance());
    
    // check contract's balance is 0
    assert(cafter == 0, "Contract's balance expected to be 0 but is " + cafter); 
    let creal = await web3.eth.getBalance(instance.address);
    assert(creal == 0, "Contract's real balance expected to be 0 but is " + creal);
  });

  
  it("should withdraw correctly", async function(){
    await instance.createPerson("Bob", 60, 200, {from: accounts[1], value: web3.utils.toWei("1", "ether")});

    let contract_before = parseFloat(await instance.balance());
    let owner_before = parseFloat(await web3.eth.getBalance(accounts[0]));
    await truffleAssert.passes(instance.withdrawAll({from: accounts[0]}));
    let owner_expected = owner_before + contract_before;// - parseFloat(txnReceipt.gasUsed);
    let owner_after = await web3.eth.getBalance(accounts[0]);
    assert(owner_after == owner_expected, 
      "Owners balance expected to be " + owner_expected + " but is " + owner_after );    
  });
});

My second test fails because i don’t substract the gas from the owners balance :frowning:
@filip Is there a way to get the gas used by the last transaction? On google i found something about a transaction receipt. Maybe i can get that somehow?

let owner_expected = owner_before + contract_before;// - parseFloat(txnReceipt.gasUsed);