Unit Testing in Truffle

Now that I have finished the Owner Test Asinment i,ve watch the solution video. Now that I know about truffleAssert.passes I have entered that into my test.

it("should set senior status correctly", async function(){
        let instance = await People.deployed();
        await instance.createPerson("Bob", 65, 190, {value: web3.utils.toWei("1", "ether")});
        let result = await instance.getPerson();
        assert(result.senior === true, "Senior level not set");
  });

// This test makes shore that only the owner of a person can delete his person
  it("Delete Unseccesfull,  User is not owner of person.", async function(){
    let instance = await People.deployed();
    await truffleAssert.fails(instance.deletePerson(accounts[0], {from: accounts[1]}), truffleAssert.ErrorType.REVERT);
  });

// This test makes shore that the owner can delet his person.
  it("Should let owner of person delet his person", async function(){
    let instance = await People.deployed();
    await truffleAssert.passes(instance.deletePerson(accounts[0], {from: accounts[0]}));
  });
});

1 Like

Value Assignment:

	let instance;

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

	it("should increase balance when creating person", async () => {
		let contractAddress = instance.address;
		let oldBalance = await web3.eth.getBalance(contractAddress)
		
		await instance.createPerson("Ted", 22, 123, { from: accounts[2] , value: web3.utils.toWei("1", "ether")})
		let newBalance = await web3.eth.getBalance(contractAddress)

		assert(newBalance > oldBalance, "Balance did not increase")
	})

	it("should withdraw all if you're owner", async () => {
		await truffleAssert.passes(instance.withdrawAll({ from:accounts[0] }) );
	})
});
1 Like
const People = artifacts.require('People');
const ta = require('truffle-assertions');

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

    let tcost_unit = "wei";
    let tcost = web3.utils.toWei("10",tcost_unit);
    let stranger = accounts[1];

    it("shouldn't create person older then 150 years", async function()
    {
        let instance = await People.deployed();
        await ta.fails(instance.createPerson("Tom Overaged", 200, 176, 
                        {value: tcost}),
                        ta.ErrorType.REVERT, "overaged registered" );
    });

    it("shouldn't create person younger then 1 years", async function()
    {
        let instance = await People.deployed();
        await ta.fails(instance.createPerson("Baby Newborn", 0, 40, 
                        {value: tcost}),
                        ta.ErrorType.REVERT, "newborn registered" );
    });

    it("should not create person if not paid at least " + tcost + " " + tcost_unit + ".", async function()
    {
        let instance = await People.deployed();
        await ta.fails(instance.createPerson("Poor Guy", 100, 176, 
                        {value: tcost-1}),
                        ta.ErrorType.REVERT, "the poor does not deserve discount");
    });

    it("should set senior status correctly", async function()
    {
        let instance = await People.deployed();
        await instance.createPerson("Tom Senior", 65, 176, {value: tcost});
        let person = await instance.getPerson();
        assert(person.senior === true, "senior status not set");
    });

    it("should set age correctly", async function()
    {
        let instance = await People.deployed();
        await instance.createPerson("Sixty Five", 65, 176, {value: tcost});
        let person = await instance.getPerson();
        assert(person.age.toNumber() === 65, "age not set correctly");
    });
    
    it("should allow owner to delete a person somebody else created", async () =>
    {
        let instance = await People.deployed();
        
        await instance.createPerson("Stranger", 1, 176, {value: tcost, from: stranger});
        let person = await instance.getPerson({ from: stranger});
        
        await instance.deletePerson(stranger);
        let person_deleted = await instance.getPerson({ from: stranger});
        
        assert(person.toString()===person_deleted.toString(),"person was not touched at all");
    });

    it("should not be allowed for non-owners to delete their own person", async () =>
    {
        let instance = await People.deployed();
        await instance.createPerson("Stranger", 30, 176, {value: tcost, from: stranger});
        await ta.fails( instance.deletePerson(stranger, {from: stranger}),
                        ta.ErrorType.REVERT, "stranger can delete a person");
    });
    /*
   it("should work", async () =>
   {
       let instance = await People.deployed();
       for (let a =0;a<=1;a++)
       {
            for (let i=0;i<3;i++)
            {
                await instance.createPerson("UserCreatedByAcc"+i, 30, 176, {value: web3.utils.toWei("10","wei"), from: accounts[i]});
                let person = await instance.getPerson({ from: accounts[i]});
                console.log('Person created by Account '+i+' ('+accounts[i]+'): '+person.name);
            }
        }

       console.log('Creators:')
       for (let j=0;j<=6;j++)
       {
           let creator = await instance.getCreator(j);
           console.log('['+j+'] ' + creator);
       }
   });
   */
});

Hey @filip, @gabba!
I know that this is soposed to be easy, but I just canā€™t find the smart contract address. I canā€™t find it anywhere! As far as Iā€™ve experienced, if the smart contract is deployed in truffle console than there is no address. Of course I could be wrong. I used google, however I could find no tutorials on finding the address for a smart contract that is deployed in the console.
Please Help.

Hi @DeCryptolorian

This test is not correct because you are not testing the balance before and after:

it ("balance should increase when a person is added", async function()

If you are removing the createPerson function your test is still working, but it shouldnā€™t. You are just testing that your variable balance is sync with the contract balance

Hi @JRB

Did you solve it ?
You needed to add the accounts as a parameter in you test:

contract("People", async function(accounts){
  it("should allow owner only to delete a person", async function () {
    let instance = await People.deployed();

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

  //Does not test actual deletion of person but access to function only
  it("should let the owner delete a person", async function () {
    let instance = await People.deployed();

    await truffleAssert.passes(instance.deletePerson(accounts[1]));
  });

  it("should delete a person", async function () {
    let instance = await People.deployed();
    await instance.createPerson("Bob", 50, 180, {
      value: web3.utils.toWei("1", "ether"),
    });

    const result = await instance.deletePerson(accounts[0]);

    truffleAssert.eventEmitted(result, "personDeleted");
  });

@gabba
Thanks for the feedback and edits!

Hi @gabba!
Sorry. Yes I did solve the asinment. I thought that I reached out to you that I did so. But I guess I did not.
Thanks!

My solution:

contract("People", async function(accounts){
// added..
  it("should not accept delete, since user is not owner of person", async function(){
    let instance = await People.deployed();
    await instance.createPerson(
      "Bob", 55, 140, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.fails(instance.deletePerson(accounts[1],
      {from: accounts[1]}), truffleAssert.ErrorType.REVERT);
  });

  it("should delete person, when user is the owner of person", async function(){
    let instance = await People.deployed();
    await (instance.deletePerson(accounts[1], {from: accounts[0]}));
  });
1 Like
    it("should increase internal balance on person creation", async () =>
    {
        let instance = await People.deployed();
        let balance_before = Number(await instance.balance());
        await instance.createPerson("Stranger", 30, 176, {value: tcost, from: stranger});
        let balance_after = Number(await instance.balance());
        let balance_expected = balance_before + Number(tcost);
        assert(balance_after == balance_expected, "internal balance is wrong after people creation")
    }); 
    
    it("should not be possible for non-owners to withdraw funds", async () =>
    {
        let instance = await People.deployed();
        await instance.createPerson("Stranger", 30, 176, {value: tcost, from: stranger});
        await ta.fails(instance.withdrawAll({from: stranger}),
                        ta.ErrorType.REVERT, null, "stranger withdrawed funds");
    });
    
    it("should set internal balance to 0 after owner withdraws", async () =>
    {
        let instance = await People.deployed();
        await instance.withdrawAll();
        let balance = Number(await instance.balance());     
        assert(balance === 0, "balance not resetted after withdrawAll()")
    });
    
    it("should no contract funds be left after owner withdrawal", async () =>
    {
        let instance = await People.deployed();
        await instance.withdrawAll();
        var contract_balance = await web3.eth.getBalance(instance.address);
        assert(contract_balance == 0, "contract balance is not correct after withdrawAll()")
    });
   
    it("should be that contract balance equals internal balance after people creation", async () =>
    {
        let instance = await People.deployed();
        await instance.createPerson("Stranger", 30, 176, {value: tcost, from: stranger});
        let contract_balance = Number(await web3.eth.getBalance(instance.address));
        let internal_balance = Number(await web3.eth.getBalance(instance.address));
        assert(contract_balance == internal_balance, "internal balance does not match contract balance")
    });
    
1 Like

My code:

contract("People", async function(accounts){
  let instance;

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

  // earlier checks skipped

  it("should increase the balance of person", async function(){
    let instance = await People.new();
    let balanceBefore = await web3.eth.getBalance(instance.address);
    await instance.createPerson(
      "Bob", 50, 160, {from: accounts[0], value: web3.utils.toWei("1", "ether")});
    let balanceAfter = await web3.utils.fromWei(await web3.eth.getBalance(instance.address), "ether");
    await truffleAssert.passes(balanceAfter - balanceBefore == 1, "increased by 1 ether");
  });
  it("should authorize owner to get balance of person and increase balance of owner", async () => {
    let instance = await People.new();
    let balanceOfOwnerBefore = await web3.utils.fromWei(await web3.eth.getBalance(accounts[0]), "ether");
    await instance.createPerson(
      "Bob", 50, 160, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
    await instance.withdrawAll({from: accounts[0]});
    let balanceOfOwnerAfter = await web3.utils.fromWei(await web3.eth.getBalance(accounts[0]), "ether");
    await truffleAssert.passes(balanceOfOwnerAfter - balanceOfOwnerBefore == 1, "increased by 1 ether");
  });
  it("should not authorize other user to get balance of person withdraw", async () => {
    let instance = await People.new();
    await instance.createPerson(
      "Bob", 50, 160, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.fails(instance.withdrawAll({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("shouldn't create a person with age over 150 years", async function(){
await truffleAssert.fails(instance.createPerson("Bob", 200, 190, {value: web3.utils.toWei("1", "ether")}), truffleAssert.ErrorType.REVERT);
});
it("shouldn't create a person without payment", async function(){
await truffleAssert.fails(instance.createPerson("Bob", 50, 190, {value: 1000}), truffleAssert.ErrorType.REVERT);
});
it("should set senior status correctly", async function(){
await instance.createPerson("Bob", 65, 190, {value: web3.utils.toWei("1", "ether")});
let result = await instance.getPerson();
assert(result.senior === true, "Senior level not set");
});
it("should set age correctly", async function(){
let result = await instance.getPerson();
assert(result.age.toNumber() === 65, "Age not set correctly");
});
it("should not allow non-owner to delete people", async function(){
let instance = await People.deployed();
await instance.createPerson("James", 46, 220, {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("James", 46, 220, {from: accounts[2], value: web3.utils.toWei("1", "ether")});
await truffleAssert.passes(instance.deletePerson(accounts[1], {from: accounts[0]}));
});
it("should add balance correctly after createPerson call with 1 ether", async function(){
let instance = await People.new();
await instance.createPerson("James", 46, 220, {from: accounts[2], value: web3.utils.toWei("1", "ether")});

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

let realBalance = await web3.eth.getBalance(instance.address);

assert(floatBalance == web3.utils.toWei("1", "ether") && floatBalance == realBalance)
});
it("should allow the owner to withdraw balance", async function(){
let instance = await People.new();
await instance.createPerson("James", 46, 220, {from: accounts[2], value: web3.utils.toWei("1", "ether")});
await truffleAssert.passes(instance.withdrawAll({from: accounts[0]}));
});
it("should not allow a non-owner to withdraw balance", async function(){
let instance = await People.new();
await instance.createPerson("James", 46, 220, {from: accounts[2], value: web3.utils.toWei("1", "ether")});
await truffleAssert.fails(instance.withdrawAll({from: accounts[2]}), truffleAssert.ErrorType.REVERT);
});
it("owners balance should increase after withdrawal", async function(){
let instance = await People.new();
await instance.createPerson("James", 46, 220, {from: accounts[2], value: web3.utils.toWei("1", "ether")});

let balanceBefore = parseFloat(await web3.eth.getBalance(accounts[0]));
await instance.withdrawAll();
let balanceAfter = parseFloat(await web3.eth.getBalance(accounts[0]));
assert(balanceBefore < balanceAfter, "Owners balance was not increased after withdrawal");

});
it("should reset balance to 0 after withdrawal", async function(){
let instance = await People.new();
await instance.createPerson("James", 46, 220, {from: accounts[2], value: web3.utils.toWei("1", "ether")});

await instance.withdrawAll();

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

let realBalance = await web3.eth.getBalance(instance.address);

assert(floatBalance == web3.utils.toWei("0", "ether") && floatBalance == realBalance, "Contract balance was not 0 after withdrawal or did not match")

})
});

Edit by @gabba

2 Likes

@filip
Why canā€™t I call the public variable balance from the deployed contract? to get the balance?

and how can we actually get the value of a public variable from the contract? please help!

Hi @loksaha1310
Any chance you are trying to call the variable directly instead of using the getter function?
https://solidity.readthedocs.io/en/v0.5.3/contracts.html#getter-functions

Another option may be you are missing await

const balance = await instance.balance();

Hard to say really, without looking at your code :slight_smile:

2 Likes

Value assignment:

it("should increase contract balance when person was added", async () => {
    const value = web3.utils.toWei("1", "ether");

    await instance.createPerson("Bob", 50, 180, {
      value: value,
    });

    const balance = await instance.balance();

    //check that internal balance was set correctly
    assert(balance.toString() === value.toString(), "Balance not correct");

    //check that contract has correct balance on chain
    const onChain = await web3.eth.getBalance(instance.address);
    assert(onChain.toString() === value.toString());
  });

it("should allow owner to withdraw contract balance", async () => {
    await instance.createPerson("Bob", 50, 180, {
      value: web3.utils.toWei("1", "ether"),
    });
    const contractBalance = await instance.balance();
    const withdrawalTx = await instance.withdrawAll();

    //check that owner balance has increased by contract balance
    const previousBlock = withdrawalTx.receipt.blockNumber - 1;
    const previousBalance = web3.utils.toBN(
      await web3.eth.getBalance(accounts[0], previousBlock)
    );

    const currentBalance = web3.utils.toBN(
      await web3.eth.getBalance(accounts[0], withdrawalTx.receipt.blockNumber)
    );

    const gasPrice = web3.utils.toBN(People.defaults().gasPrice);
    const txCosts = gasPrice.mul(web3.utils.toBN(withdrawalTx.receipt.gasUsed));

    assert(
      currentBalance.sub(previousBalance).add(txCosts).toString() ===
        web3.utils.toBN(contractBalance).toString()
    );

    //chech that contract balance is set to 0
    const balance = await instance.balance();
    assert(balance.toString() === "0", "Contract balance is wrong");

    //check that contract has correct balance on chain
    const onChain = await web3.eth.getBalance(instance.address);
    assert(onChain.toString() === "0");
  });
1 Like

Hey @filip, @gabba!
I know that this is soposed to be easy, but I just canā€™t find the smart contract address. I canā€™t find it anywhere! As far as Iā€™ve experienced, if the smart contract is deployed in truffle console than there is no address. Of course I could be wrong. I used google, however I could find no tutorials on finding the address for a smart contract that is deployed in the console.
Please Help.

Hey @Crypto_James!
If you use preformated text to show your code than it will be easier to read. As is, reading your code is sort of dificult to understand.
prefromatted_text-animated

1 Like

Hey @Karolis_Abramavicius!
If you use preformated text to show your code than it will be easier to read. As is, reading your code is sort of dificult to understand.
prefromatted_text-animated

1 Like

For the sake of getting more practice, I changed a few things here and there. First I changed the migrations file and the contract itself in order to be able to add and display account owners with various account addresses. The reason for this change was my observation that in the process of deploying a contract the only address that msg.sender makes available is the address of the deployer (who, by definition, is also the owner). So a natural question to which I do not know the answer yet is how the address value of msg.sender can be varied. (update:: in watching the solution video, I saw that this problem is solved by using the ā€œfromā€ specification.)

My solution to the owner-only test is to verify that the personDeleted event has occurred and that the deletedBy parameter is equal to the address of the contract owner (which I found to be equal to accounts[0]). The test seems to be working fine and the tx variable seems to provide the correct transaction identifier. I wonder though what would happen if the deletePerson function had an actual return value. In that case I would expect tx to simply be equal to that return value. So how do we produce correct transaction identifiers in general?

When you look at the code in my migrations file, you will notice that there is some code duplication. I tried to eliminate that duplication by means of an asynchronous function, but when I did so, the migration command was no longer working properly. I believe that there was a problem in coordinating async functions, but I havenā€™t figured out yet how to resolve that problem.

As a note at the side I would like to add that the deletePerson function does not remove a personā€™s address mapping but merely a personā€™s variable values. In a real-world situation we probably would want to remove the address mapping as well. Is that correct?

So here is my modified contract:

 import "./Ownable.sol";
 pragma solidity 0.5.12;

 contract People is Ownable{

    struct Person {
        uint id;
        string name;
        uint age;
        uint height;
        bool senior;
   }

   event personCreated(string name, bool senior);
   event personDeleted(string name, bool senior, address deletedBy);

   uint public balance;
   uint public numberOfPersons = 0;

   modifier costs(uint cost){
       require(msg.value >= cost);
    _;
   }

   mapping (address => Person) private people;
   address[] private creators;

   function getOwner() public view returns(address){
   return owner;
   }

   function getCreators(uint n) public view returns(address){
   return creators[n];
   }

   function getNumberOfPersons()public view returns(uint){
   return numberOfPersons;
   }

   function createPerson(string memory name, uint age, uint height, address 
   personAddress) public payable costs(1 ether){
     require(age < 150, "Age needs to be below 150");
     require(msg.value >= 1 ether);
     balance += msg.value;
     numberOfPersons++;

    //This creates a person
    Person memory newPerson;
    newPerson.name = name;
    newPerson.age = age;
    newPerson.height = height;

    if(age >= 65){
       newPerson.senior = true;
   }
   else{
       newPerson.senior = false;
   }

    insertPerson(newPerson, personAddress);
    creators.push(personAddress);

    assert(
        keccak256(
            abi.encodePacked(
                people[personAddress].name,
                people[personAddress].age,
                people[personAddress].height,
                people[personAddress].senior
            )
        } 
        ==
        keccak256(
            abi.encodePacked(
                newPerson.name,
                newPerson.age,
                newPerson.height,
                newPerson.senior
            )
        )
    );
    emit personCreated(newPerson.name, newPerson.senior);
   }
   function insertPerson(Person memory newPerson, address newPersonAddress) private {
       address creator = newPersonAddress;
       people[creator] = newPerson;
   }
   function getPerson(address personAddress) public view returns(string memory name, 
   uint age, uint height, bool senior){
       //address creator = msg.sender;
       return (people[personAddress].name, people[personAddress].age, 
   people[personAddress].height, people[personAddress].senior);
   }
   function deletePerson(address creator) public onlyOwner {
     string memory name = people[creator].name;
     bool senior = people[creator].senior;
     numberOfPersons--;

   delete people[creator];
   assert(people[creator].age == 0);
   emit personDeleted(name, senior, owner);
  }
  function getCreator(uint index) public view onlyOwner returns(address){
     return creators[index];
  }
  function withdrawAll() public onlyOwner returns(uint) {
   uint toTransfer = balance;
   balance = 0;
   msg.sender.transfer(toTransfer);
   return toTransfer;
  }
}

And here is my modified migrations file:

 const People = artifacts.require("People");
 module.exports = function(deployer, networks, accounts) {
   deployer.deploy(People).then(async function(instance){
     for(n=0; n<10; n++){
       console.log("Account #" + n + ": " + accounts[n] + "\n");
     }
     let ownerAddress = await instance.getOwner();
     console.log("Owner: " + ownerAddress + "\n");
     for(n=0; n<=2; n++){
       await instance.createPerson("Babette" + n, 16+n, 160, accounts[n], {value: 
       web3.utils.toWei("10", "ether")});
     }
     let  loopNumber = await instance.getNumberOfPersons();
     for(n=0; n<loopNumber; n++){
     let creatorAddress = await instance.getCreators(n);
     let person = await instance.getPerson(creatorAddress);
     console.log("Person #" + n +": name: " + person.name + "; age: " + person.age + "; 
        address: " + creatorAddress + "\n");
     }
    await instance.deletePerson(accounts[loopNumber-1]);
    loopNumber = await instance.getNumberOfPersons();
    for(n=0; n<loopNumber; n++){
       let creatorAddress = await instance.getCreators(n);
       let person = await instance.getPerson(creatorAddress);
       console.log("Person #" + n +": name: " + person.name + "; age: " + person.age + "; 
           address: " + creatorAddress + "\n");
       }
    });
  };

And finally, here is my test file:

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

contract("People", async function(accounts){
it("shouldn't create a person older than 150 years", async function(){
   let instance = await People.deployed();
   await truffleAssert.fails(instance.createPerson("Bob", 200, 190, accounts[0], {value: 
      web3.utils.toWei("1", "ether")}),truffleAssert.ErrorType.REVERT);
   });
   it("should demand proper payment", async function(){
      let instance = await People.deployed();
      await truffleAssert.fails(instance.createPerson("Bob", 70, 190, accounts[0], {value: 
         1000}),truffleAssert.ErrorType.REVERT);
   });
   it("should set senior status correctly", async function(){
      let instance = await People.deployed();
      await instance.createPerson("Bob", 70, 190, accounts[0], {value: web3.utils.toWei("1", 
         "ether")});
      let result = await instance.getPerson(accounts[0]);
      assert(result.senior===true, "senior status not properly set");
   });
   it("should emit delete-person event with owner address", async function(){
      let instance = await People.deployed();
      await instance.createPerson("Babette", 16, 160, accounts[5], {value: 
         web3.utils.toWei("1", "ether")});
      let tx = await instance.deletePerson(accounts[5]);
      truffleAssert.eventEmitted(tx, 'personDeleted', {deletedBy: accounts[0]});
    });
 });