Unit Testing in Truffle

Hey @mervxxgotti

console.log is not an async function, you should not use ‘await’.

Also the javascript console prints data even without console.log(), you see the same behaviour if you open it in your browser
Screenshot 2021-03-12 at 09.26.39

It’s really strange that you get not a number when you parse the bn 41, can you post your code here so that I can copy paste/ deploy and check?

cheers,
Dani

1 Like
pragma solidity 0.5.12;
//pragma experimental ABIEncoderV2;
import "./Ownable.sol";

contract People is Ownable {

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

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

    uint public balance;

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

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

    //costs 1 ether to run
    function createPerson(string memory _name, uint _age, uint _height) public payable costs(1 ether) {
        require(_age < 150, "TOO OLD!");
        //require(msg.value >= 1 ether);    //requires 1 wei to execute
        //balance += msg.value;       //add to balance value that was sent to this function/contract

        Person memory newPerson;
        newPerson.name = _name;
        newPerson.age = _age;
        newPerson.height = _height;

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

        insertPerson(newPerson);
        creators.push(msg.sender);

        //assert using to == compare hash of both using keccak256 hash function
        //essentially checking if people[msg.sender] == newPerson
        assert(
            keccak256(
                abi.encodePacked(
                    people[msg.sender].name,
                    people[msg.sender].age,
                    people[msg.sender].height,
                    people[msg.sender].senior
                )
            )
            ==
            keccak256(
                abi.encodePacked(
                    newPerson.name,
                    newPerson.age,
                    newPerson.height,
                    newPerson.senior
                )
            )
        );
        emit personCreated(newPerson.name, newPerson.age, newPerson.height, newPerson.senior);
    }

    function insertPerson(Person memory _person) private {
        address creator = msg.sender;
        people[creator] = _person;
    }

    /*for use with ABIEncoderV2
    function getPerson() public view returns (Person memory) {
        return people[msg.sender];
    }*/

    function getPerson() public view returns (string memory, uint, uint, bool) {
        address creator = msg.sender;
        return (people[creator].name, people[creator].age, people[creator].height, people[creator].senior);
    }

    function deletePerson(address creator) public onlyOwner {
        string memory name = people[creator].name;
        bool senior = people[creator].senior;

        delete people[creator];
        assert(people[creator].age == 0);
        emit personDeleted(name, senior, msg.sender);
    }

    function getCreator(uint index) public view onlyOwner returns (address) {
        return creators[index];
    }

    function withdrawAll() public onlyOwner returns (uint) {
        uint toTransfer = balance;      //make temp var to send, reset balance to 0, done for security reasons
        balance = 0;
        msg.sender.transfer(toTransfer);    //transfer can only be used by an address, transfers to address, which in this case
                                            //is the owner, or msg.sender if already here. Reverts if transfer fails
        /*
        msg.sender.send(toTransfer);        //this is the same as transfer but returns false if fails instead of reverts.
                                            //hence, this requires manual reverting or else funds are lost.
        if(msg.sender.send(toTransfer)) {
            return toTransfer;
        } else {
            balance = toTransfer;
            return 0;
        }
        */
        return toTransfer;
    }
}

Dani! I think I got it!

I took a look at Filip’s uploaded code and this was the difference I spotted but am still confused by:

  function getPerson() public view returns(string memory name, uint age, uint height, bool senior){
        address creator = msg.sender;
        return (people[creator].name, people[creator].age, people[creator].height, people[creator].senior);
    }

compared to mine:

  function getPerson() public view returns(string memory, uint, uint, bool){
        address creator = msg.sender;
        return (people[creator].name, people[creator].age, people[creator].height, people[creator].senior);
    }

I added names to the returns arguments and it returned correctly now but how come in other instances when using returns() only the types are defined without names? For example in:

function getCreator(uint index) public view onlyOwner returns (address) {
        return creators[index];
    }

    function withdrawAll() public onlyOwner returns (uint) {
        uint toTransfer = balance;      //make temp var to send, reset balance to 0, done for security reasons
        balance = 0;
        msg.sender.transfer(toTransfer);    //transfer can only be used by an address, transfers to address, which in this case
                                            //is the owner, or msg.sender if already here. Reverts if transfer fails
        return toTransfer;
    }

Why is this and when are we required to add names as well as types in the returns() part of the function header? Do we do this when returning multiple variables? But I remember before we had something like returns (uint, uint) in other header functions.

Do we have to define return variable names when returning multiple struct members specifically?

it("should not allow delete person unless owner", async function() {
    let instance = await People.deployed();

    //create person from account 1 since account 0 is contract owner defined in deployment
    await instance.createPerson("Bob", 65, 90, {from: accounts[1], value: web3.utils.toWei("1", "ether")});

    //test failure, account 1 cannot delete even if it made person, only owner (address 0) can delete
    await truffleAssert.fails(
      instance.deletePerson(accounts[1], {from: accounts[1]}),
      truffleAssert.ErrorType.REVERT
    );
  })

  it("should allow owner to delete people", async function(){
    let instance = await People.deployed();

    await truffleAssert.passes(
      instance.deletePerson(accounts[1], {from: accounts[0]})
    );
  })
1 Like

Hey @mervxxgotti

Do we have to define return variable names when returning multiple struct members specifically?

It is not mandatory to define names of your returned values.
Only types are mandatory.

Cheers,
Dani

1 Like

Hey, I haven’t gotten the test comparing balances before and after withdrawal working yet, but I have some questions about “await” as I think it has something to do with that.

So when I use vs not use “await”,
let a = web3.eth.getBalance(accounts[0]) vs let b = await web3.eth.getBalance(accounts[0])

both return the account balance in wei when I check the values of a and b, but then they behave very differently.

How come when when I parse a (without await) it doesn’t read a as a string, but then it does with b (with await?

If this is the case, how come when I use:

let newPerson = instance.getPerson()

it only actually assigns the values to newPerson if I defined the return names in the getPerson() header? When I didn’t have the names and only had the types in the returns() part, newPerson.age and newPerson.height were returning as NaN?

It’s working now but I just am curious and want to know for my own knowledge.

I created a test and wanted to assert that every wei is transferred to the address it should be.
However, it seems I am calculating something wrong here:

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

contract("People", async (accounts) => {

    let instance;
    let oneEther = web3.utils.toWei("1", "ether");

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

    // ... several other tests...

    it("preventy anyone but owner to withdraw balance", async () => {
        let twoEther = oneEther * 2;
        let owner = accounts[0];
        let alex = accounts[1];
        let mike = accounts[2];

        let initialOwnerBalance = parseFloat(await web3.eth.getBalance(owner));
        await instance.createPerson("Alex", 36, 180, {value: twoEther, from:alex});
        await instance.createPerson("Mike", 32, 185, {value: oneEther, from:mike});

        // Act
        await truffleAssert.fails(instance.withdrawAll({from:alex}));
        await truffleAssert.fails(instance.withdrawAll({from:mike}));
        let withdrawn = await instance.withdrawAll.call(({from:owner}));
        let txn = await instance.withdrawAll({from:owner});

        // Assert
        // let gasUsed = parseFloat(txn.receipt.gasUsed);
        let threeEther = oneEther*3;
        let ownerBalance = parseFloat(await web3.eth.getBalance(owner));
        let contractBalance = parseFloat(await web3.eth.getBalance(instance.address));
        assert(withdrawn == threeEther, "should withdraw 3 eth but was "+withdrawn);
        // why the hell is there a difference of 0,00041?? GAS?
        assert(ownerBalance == (initialOwnerBalance+withdrawn), 
            "initial: "+web3.utils.fromWei(""+initialOwnerBalance)+" + withdrawn: "+web3.utils.fromWei(""+withdrawn)+" = after: "+web3.utils.fromWei(""+ownerBalance)); 
        assert(ownerBalance > initialOwnerBalance, "owner should have more eth now...");
        assert(contractBalance == 0, "contract balance should be drained but was " + contractBalance);
    })

});

So apparently, when looking at the owners balance, there is some wei missing. Can anybody explain why?
If ETH may be worth 20K some day, I would rather know that my contract sends just the right amount of wei obviously :wink:

Hey @gentledepp

The owner paid gas to call instance.withdrawAll :slight_smile:

Cheers,
Dani

anytime i want to test my contract it keep failing

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

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

contract(“People”, async function(){
it(“should process smoothly” , async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson(“gabby” ,70 , 160 , {value: web3.utils.toWei(“1”, “ether”)}),truffleAssert.ErrorType.REVERT);
})
it(“should undergo correctly” , async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson(“lisa” , 68 ,200 , {value: web3.utils.toWei(“1”, “ether”)}),truffleAssert.ErrorType.REVERT);
})
it(“should undergo correctly” , async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson(“lisa” , 68 ,200 , {value: 1000000000000000000 , from: account[0]}),truffleAssert.ErrorType.REVERT);
})
it("should not be allowed to delete person " , async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson(“andrew” , 98 ,200 , { from: account[2], value: web3.utils.toWei(“1”, “ether”)}),truffleAssert.ErrorType.REVERT);
await truffleAssert.fails(instance.deletePerson(accounts[2], {from: account[2]}),truffleAssert.ErrorType.REVERT);
})
it(“onlyowner should be allowed to delete person” , async function(){
let instance = await People.deployed();
await truffleAssert.passes(instance.deletePerson(account[2], { from: account[0]}));
})

});

type or paste code here

Hi @beloved

Please follow this faq to post readable code: FAQ - How to post code in the forum

Can you tell us which test is failing and what’s the error message? It’s also helpful post your smart contract.

Regards,
Dani

Here is the contract coding u asked for

pragma solidity 0.5.12;

contract Ownable{
address public owner;
modifier onlyOwner(){
require(msg.sender == owner);
_; //continue execution
}

constructor() public {
owner = msg.sender;
}
}

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;

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

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

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

  //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);
  creators.push(msg.sender);

  assert(
      keccak256(
          abi.encodePacked(
              people[msg.sender].name,
              people[msg.sender].age,
              people[msg.sender].height,
              people[msg.sender].senior
          )
      )
      ==
      keccak256(
          abi.encodePacked(
              newPerson.name,
              newPerson.age,
              newPerson.height,
              newPerson.senior
          )
      )
  );
  emit personCreated(newPerson.name, newPerson.senior);

}
function insertPerson(Person memory newPerson) private {
address creator = msg.sender;
people[creator] = newPerson;
}
function getPerson() public view returns(string memory name, uint age, uint height, bool senior){
address creator = msg.sender;
return (people[creator].name, people[creator].age, people[creator].height, people[creator].senior);
}
function deletePerson(address creator) public onlyOwner {
string memory name = people[creator].name;
bool senior = people[creator].senior;

 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;
}
}


when ever i test it it doesn't pass 
here is the test coding

const People = artifacts.require("People");

 const truffleAssert = require("truffle-assertions");

 contract("People", async function(){
   it("should process smoothly" , async function(){
     let instance = await People.deployed();
     await truffleAssert.fails(instance.createPerson("gabby" ,70 , 160 , {value: web3.utils.toWei("1", "ether")}),truffleAssert.ErrorType.REVERT);
   })
   it("should undergo correctly" , async function(){
    let instance = await People.deployed();
    await truffleAssert.fails(instance.createPerson("lisa" , 68 ,200 , {value: web3.utils.toWei("1", "ether")}),truffleAssert.ErrorType.REVERT);
  })
   it("should undergo correctly" , async function(){
    let instance = await People.deployed();
    await truffleAssert.fails(instance.createPerson("lisa" , 68 ,200 , {value: 1000000000000000000 , from: account[0]}),truffleAssert.ErrorType.REVERT);
})
it("should not be allowed to delete person " , async function(){
 let instance = await People.deployed();
 await truffleAssert.fails(instance.createPerson("andrew" , 98 ,200 , { from: account[2], value: web3.utils.toWei("1", "ether")}),truffleAssert.ErrorType.REVERT);
 await truffleAssert.fails(instance.deletePerson(accounts[2], {from: account[2]}),truffleAssert.ErrorType.REVERT);
})
it("onlyowner should be allowed to delete person" , async function(){
    let instance = await People.deployed();
       await truffleAssert.passes(instance.deletePerson(account[2], { from: account[0]}));
})

 });

type or paste code here

type or paste code here
const People = artifacts.require("People");
const truffleAssert = require("truffle-assertions");

contract("People", async function(accounts) {
	it("Only the owner should be able to delete a person.", async function(){
		let instance = await People.deployed();
		await instance.createPerson("Bob", 65, 170, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
		await truffleAssert.fails(instance.deletePerson(accounts[1], {from: accounts[1]}), truffleAssert.ErrorType.REVERT);
	})

	it("Only the owner should be able to delete person.", async function(){
		let instance = await People.deployed();
		await truffleAssert.passes(instance.deletePerson(accounts[2], {from: accounts[0]}));
	})
2 Likes

Here is my value assignment. The address of the contract is retrieved using the instance.address and parseInt is used to convert the value to an integer to perform the logic required to check the balances.

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

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

	it("Balance is correctly updated when new person is created.", async function(){
		let previous_balance = await web3.eth.getBalance(instance.address);
		await instance.createPerson("Bob", 65, 170, {value: web3.utils.toWei("1", "ether")});
		let current_balance = await web3.eth.getBalance(instance.address);
		assert(parseInt(current_balance) === parseInt(previous_balance) + 1000000000000000000);
	});

	it("Balance should be zero after owner performs withdrawAll", async function(){
		let previous_balance = await web3.eth.getBalance(instance.address);
		await instance.withdrawAll({from: accounts[0]});
		let current_balance = await web3.eth.getBalance(instance.address);
		assert(parseInt(current_balance) === 0);
	})

	it("Accounts other than the owner should not be able to withdrawAll", async function(){
		let previous_balance = await web3.eth.getBalance(instance.address);
		await truffleAssert.fails(instance.withdrawAll({from: accounts[1]}));
	})
	
})
2 Likes

From the video: Creating wallet test.
When I do:

contract("Dex", accounts => {
    it("Should only be possible for owner to add tokens", async ()=>{
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        
        await truffleAssert.passes(
            await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]} )
        );  

        await truffleAssert.reverts(
            await dex.addToken(web3.utils.fromUtf8("AAVE"), link.address, {from: accounts[1]} )
        ); 
    } )

I don’t get a pass. I get:


  Contract: Dex
    1) Should only be possible for owner to add tokens
    > No events were emitted


  0 passing (6s)
  1 failing

  1) Contract: Dex
       Should only be possible for owner to add tokens:
     Error: Returned error: VM Exception while processing transaction: revert Ownable: caller is not the owner -- Reason given: Ownable: caller is not the owner.
      at Context.<anonymous> (C:\Users\rapha\Documents\NFT\ETH201\DEX\test\wallettest.js:15:23)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)

The error message is correct. But I wanted it to revert.
Why does it behave differently than @filip’s code?
Also I get 0 passed. But the first test should have passed…

I figured it out, each test should start with it("…") again.
Works like a charm.

Alternative not using truffle assertions but native JS is try catch.
Like here:
https://ethereum.stackexchange.com/questions/48627/how-to-catch-revert-error-in-truffle-test-javascript/48629

2 Likes

Please how do I code a 40% return to an investor after 30 days everytime he deposit in to the contract using solidity
I have used block.timestamp but I want to put it all together how do I do it?

please whenever i deploy this contract using remix
it deploy successfully
whenever i call the sendeth function in the contract eth is not being sent to the address
and when i call the get balance function the balance still show that the eth recieved is still there please what should i do that when ever i call the sendeth function it actually send eth and the address i send the eth to recieves it
HERE IS THE CONTRACT:
pragma solidity 0.5.12;

contract Transfer{
address public owner = msg.sender;

 function sendeth(address payable _to) public payable {
  _to.transfer(msg.value);   
  require(owner == msg.sender);
 }
  
  function recieve() public payable{
   require(msg.value > 0); 
   
  }

 function getBalance() view public  returns (uint){
  return address(this).balance; 
 }

}`

Preformatted text

`

Blockquote

Actually the problem with the code here is that you are awaiting the dex.addToken() call inside the truffleAssert.reverts() function. truffleAssert.revert() is actually expecting a promise that it can resolve and deal with the outcome. But when you use await inside of it. It actually resolves to the error that is returned by the dex.addToken() now allowing the truffleAssertions to handle it, so the execution breaks even before truffleAssertions can do its thing. the correct usage is given below…

await truffleAssert.reverts(dex.addToken(web3.utils.fromUtf8("AAVE"), link.address, {from: accounts[1]} )); 

So you don’t need the implementation mentioned in the stackexchange question.

1 Like