Unit Testing in Truffle

Owner Test Assignment solution

I went through this assignment over and over many times. Now I am confident with the solution after a tons of tests.

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

contract("People", async function (accounts) {
    it("Should set senior status properly", async () => {
        let instance = await People.deployed();
        await instance.createPerson("Barbara", 65, 171, {
            value: web3.utils.toWei("1", "ether"),
            from: accounts[0]
        });
        let result = await instance.getPerson();
        assert(result.senior === true && result.age.toNumber() === 65, "Senior level not set");
    });
    it("Sholudn't be deleted by an another account owner", async () => {
        let instance = await People.deployed();
        await truffleAssert.fails(instance.deletePerson(accounts[0], {
                from: accounts[5]
            }),
            truffleAssert.ErrorType.REVERT);
    });
    it("Should be able to delete your own account", async () => {
        let instance = await People.deployed();
        let person = await instance.getPerson();
        await truffleAssert.passes(instance.deletePerson(accounts[5], {
            from: accounts[0]
        }))
        assert(person.age.toNumber() !== 5 && person.name !== '',
            "A person has to have setted name and age 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 was not executed correctly");
    });
});

@filip
I am getting this error, and I am not sure how to fix it:

  1. Contract: People
    “before all” hook:
    Error: People has not been deployed to detected network (network/artifact mismatch)
    at Object.checkNetworkArtifactMatch (/usr/local/lib/node_modules/truffle/build/webpack:/packages/contract/lib/utils/index.js:271:1)
    at Function.deployed (/usr/local/lib/node_modules/truffle/build/webpack:/packages/contract/lib/contract/constructorMethods.js:85:1)
    at runMicrotasks ()
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at Context. (test/PeopleTest.js:9:16)

I don’t know what happened. I re-saved the .js file, re-complied and it works now.

2 Likes

New tests from Filip’s video do not work for me. I get the same TypeError:

1) Contract: People
       should not allow non-owner to delete people:
     TypeError: Cannot read property '2' of undefined
      at Context.<anonymous> (test\peopletest.js:31:65)
      at process._tickCallback (internal/process/next_tick.js:68:7)

  2) Contract: People
       should allow the owner to delete people:
     TypeError: Cannot read property '2' of undefined
      at Context.<anonymous> (test\peopletest.js:36:64)
      at process._tickCallback (internal/process/next_tick.js:68:7)

For code:

it("should not allow non-owner to delete people", async function(){
    let instance = await People.deployed();
    await instance.createPerson("Lisa", 35, 160, {from: accounts[2], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.fails(instance.deletePerson(accounts[2], {from: accounts[2]}), truffleAssert.ErrorType.REVERT);
  });
  it("should allow the owner to delete people", async function(){
   let instance = await People.new();
   await instance.createPerson("Lisa", 35, 160, {from: accounts[2], value: web3.utils.toWei("1", "ether")});
   await truffleAssert.passes(instance.deletePerson(accounts[1], {from: accounts[0]}));
  });

I had been getting this error for 3 days already. Please advice.

Please send me what versions you are running. I believe there might be a problem.
Here is mine:

truffle(ganache)> version
Truffle v5.0.42 (core: 5.0.42)
Solidity - 0.5.12 (solc-js)
Node v10.18.0
Web3.js v1.2.1

I did downgrade Node.js because I had issues before.

1 Like

Honestly, I couldn’t figure this one out… the Owner test assignment.
I was trying something like, but it wasn’t compiling:

 it("Allow owner to delete Person", async function(){

    await accounts[0].createPerson("Bob", 50, 190, {value: web3.utils.toWei("1", "ether")});
    await accounts[0].deletePerson(accounts[0].address);
   
    assert(accounts[0].age==0, "Test to see if we deleted");
  });

I couldn’t figure out how to pass the address as an argument to the createPerson function.

I see the concept I was missing was passing the account like this: {from: accounts[2],…}

await instance.createPerson("Lisa", 35, 160, {from: accounts[2], value: web3.utils.toWei("1", "ether")});

@filip
How would you have searched for this in the internet?

I need a bit of clarification with defining value. In the contract, we write:

function createPerson(string memory name, uint age, uint height) public payable costs(1 ether) {

        require (age < 150, "Age not legit");
        require (msg.value >= 1 ether);
        balance += msg.value;

Value is just 1 ether and it works.
However, in peopletest.js, to set the value to 1 ether, we need this:

{value: web3.utils.toWei("1", "ether")}

Is it because Javascript doesn’t know what an ether is, but Solidity does?

Yes, that is correct. I believe sooner or later there will be library with much more simpler implementation.

1 Like

@rostyslavdzhohola
The error you are getting is similar in the question you posted here:

I have answered it there, please refer: :slight_smile:

2 Likes

@elterremoto
Great to see you figured out the missing part! :+1:

Information on internet (google) is pretty huge with multiple sources, every person finds a different channel to the right answer.

For me personally, I would have search how to call a function in web3 solidity example

1 Like

@MarcisB

Yes, JS doesn’t know what is an ether, web3 knows which is a js library hence we use web3 function.

1 Like

@rostyslavdzhohola
You can check for web3 library updates and even create task, what is called issues in GitHub for having a better implementation, if you feel it can be improved.

Web3 is an open-source library, any contribution to it will make you famous (and rich) :slight_smile:

check out: https://github.com/ethereum/web3.js/issues

2 Likes

@rostyslavdzhohola
Node v10 or below works fine.

1 Like

This is what worked for me:

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

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

  it("should allow only owner to delete", async function(){
    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 [1], {from: accounts [1]}), truffleAssert.ErrorType.REVERT);
  });
})

I also tried to specify which account should be considered the owner, like we could in Remix:

    let instance = await People.deployed ({from: accounts [2]});

but in that case, the test always passed and I noticed that, in Ganache, no transactions appear for account 2. Apparently the deployer is still account 0.

To be honest, I peaked in the forum for this part:

{from: accounts [1]}

I don’t really get why it is exactly like that. Is it a pure Javascript expression or does it come from web3?

Hi all,

and thx @filip for the nice course! While playing around with the testing of the peoples contract I found some weird behavior that i dont completely understand. If I test the edge case with

await truffleAssert.fails(instance.createPerson(“Bob”,60,180,{value:(10**18-1)}),truffleAssert.ErrorType.REVERT);

or

await truffleAssert.fails(instance.createPerson(“Bob”,60,180,{value:(10**18-10)}),truffleAssert.ErrorType.REVERT);

i get a “did not fail”. Only starting from

await truffleAssert.fails(instance.createPerson(“Bob”,60,180,{value:(10**18-100)}),truffleAssert.ErrorType.REVERT);

the test passes as it should. I did not change the code (still uses the costs(1 ether) modifier). Is this a bug in solidity or (whats more probable) am I getting something wrong here? Can you reproduce this behavior?

Thx already!

I tried to make this as short as possible and reuse code, while keeping all the tests we had done so far.
As we were creating Bob a lot, I decided it should be a function that we call every time we need to create a person. It takes age, height and amount of ETH as arguments.

Regarding balance, we need to test if the contract is counting it correctly, if correct amount is added upon createPerson() and if it is reset after the owner withdraws all. For that, I defined a balanceTest() function which we call on various moments during one People.deployed instance.

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

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

  let instance;

  let age;
  let height;
  let value;
  async function bob(age, height, value){
    await instance.createPerson("Bob", age, height, {value: web3.utils.toWei(value.toString(), "ether"), from: accounts [1]});
  };

  function balanceTest (){
    it("counted balance should match the balance on blockchain", async function(){
      let countedBalance = await instance.balance();
      let chainBalance = await web3.eth.getBalance(People.address);
      assert(parseInt(countedBalance) === parseInt(chainBalance));
    });
  };

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

  balanceTest(); // compares balances at the beginning

  it("shouldn't create a person with age over 150", async function(){
    await truffleAssert.fails(bob(200, 190, 1), truffleAssert.ErrorType.REVERT);
  });
  it("shouldn't create a person if payment is less than 1 ETH", async function(){
    await truffleAssert.fails(bob(65, 190, 0.1), truffleAssert.ErrorType.REVERT);
  });
  it("should set age and senior status correctly", async function(){
    await instance.createPerson("Bob", 65, 190, {value: web3.utils.toWei("1", "ether")});
    // bob(65, 190, 1); makes test to fail
    // await instance.createPerson("Bob", 65, 190, {value: web3.utils.toWei("1", "ether"), from: accounts [1]}); makes test to fail

    let result = await instance.getPerson();
    assert(result.senior === true && result.age.toNumber() === 65, "Age or senior level not set");
  });

  balanceTest(); // as this is first time we create person successfully, let's check balance again

  it("should allow only owner to delete", async function(){
    bob(65, 190, 1);
    await truffleAssert.fails(instance.deletePerson(accounts [1], {from: accounts [1]}), truffleAssert.ErrorType.REVERT);
  });
  it("should allow the owner to delete", async function(){
    let instance = await People.new ();
    bob(65, 190, 1);
    await truffleAssert.passes(instance.deletePerson(accounts [1], {from: accounts [0]}), truffleAssert.ErrorType.REVERT);
  });
  // cannot be merged with previous test as new instance is needed

  it("should allow owner but not others to withdraw all", async function(){
    await truffleAssert.fails(instance.withdrawAll({from: accounts [1]}), truffleAssert.ErrorType.REVERT);
    await truffleAssert.passes(instance.withdrawAll({from: accounts [0]}), truffleAssert.ErrorType.REVERT);
  });
  // doesn't need a new instance as withdrawAll() works also if balance is 0

  it("withdrawal should drain contract balance on blockchain", async function(){
    assert(parseInt(await web3.eth.getBalance(People.address)) === 0);
  });

  balanceTest(); // to make sure counted balance is reset as well

  it("withdrawal should increase owner's balance", async function(){
    bob(65, 190, 1);
    let ownerBalance = await web3.eth.getBalance(accounts [0]);
    await instance.withdrawAll({from: accounts [0]});
    let ownerBalanceAfter = await web3.eth.getBalance(accounts [0]);
    assert(parseInt(ownerBalanceAfter) > parseInt(ownerBalance));
  });
})

With the code above, all the tests pass for me, and also fail if I make a deliberate error.
On it(“should set age and senior status correctly” I am not using the bob() function as it caused the test to fail. I found out that if I add from: accounts [1]}), it also causes the test to fail (like said in the comment). I couldn’t figure it out so I left it like that.

  it("should not delete person as non-owner", async function(){
    let instance = await People.deployed();
    let nonOwner = accounts[1];
    await instance.createPerson("Bob", 65, 190, {from: nonOwner, value: web3.utils.toWei("1", "ether")});
    await truffleAssert.fails(instance.deletePerson(nonOwner, {from: nonOwner}), truffleAssert.ErrorType.REVERT);
  })
  it("should delete person as owner", async function(){
    let instance = await People.deployed();
    let owner = accounts[0];
    let nonOwner = accounts[1];
    await instance.createPerson("Bob", 65, 190, {from: nonOwner, value: web3.utils.toWei("1", "ether")});
    await truffleAssert.passes(instance.deletePerson(nonOwner, {from: owner}));
  })
	it("should delete person when called by owner", async function(){
		let instance = await People.deployed();
		await instance.createPerson("Bob", 50, 190, {value: web3.utils.toWei("1", "ether"), from: accounts[0]});

		await instance.deletePerson(accounts[0]);

		let result = await instance.getPerson({from: accounts[0]});

		assert(result.age.toNumber() === 0, "Expected to have deleted person");
	});

	it("shouldn't delete person when called by other than owner", async function(){
		let instance = await People.deployed();
		await instance.createPerson("Bob", 50, 190, {value: web3.utils.toWei("1", "ether"), from: accounts[1]});

		await truffleAssert.fails(instance.deletePerson(accounts[1], {from: accounts[1]}), truffleAssert.ErrorType.REVERT);
	});
	let oneEther = parseFloat(web3.utils.toWei("1", "ether"))

	it("should deduct 1 Ether + gas from balance when creating a person", async function(){
		let initialBalance = parseFloat(await web3.eth.getBalance(accounts[2]));
		let expectedBalanceBeforeGas = initialBalance - oneEther

		await instance.createPerson("Bob", 50, 190, {value: oneEther, from: accounts[2]});

		let newBalance = parseFloat(await web3.eth.getBalance(accounts[2]));

		assert(newBalance < expectedBalanceBeforeGas, "Expected balance to drop more than 1 Ether");
	});

	it("should withdraw balance when called by owner", async function(){
		let instance = await People.new();

		await instance.createPerson("Bob", 50, 190, {value: oneEther, from: accounts[1]});

		let initialBalance = parseFloat(await instance.balance());

		assert(initialBalance === oneEther, "Expected to have 1 Ether balance before withdraw");

		await instance.withdrawAll();

		let emptyBalance = parseFloat(await instance.balance());

		assert(emptyBalance === 0, "Expected to have empty balance after withdraw");
	});

	it("shouldn't withdraw balance when called by non-owner", async function(){
		await truffleAssert.fails(instance.withdrawAll({from: accounts[1]}), truffleAssert.ErrorType.REVERT);
	});

Hey @smileBTC

Please post you createPerson function. If it uses a modifier, post that one too.

Make sure to post you code as shown below.

image