Unit Testing in Truffle

You could, yes. It might be a bit overkill for this assignment. But I would love it if you tried. You can get the receipt from the withdrawAll call once the promise is resolved. So you should be able to get the receipt with:

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

You should probably anyway remove the truffleAssert.passes from this test. You generally don’t want multiple assert statements in the same test.

1 Like

Thanks. I understand the overkill :slight_smile: but i want to see if i understand the procedure and i looks like i dont :thinking: i changed it to the following:

    let contract_before = parseFloat(await instance.balance());
    let owner_before = parseFloat(await web3.eth.getBalance(accounts[0]));
    let txnReceipt = await instance.withdrawAll({from: accounts[0]});
    let gas = parseFloat(txnReceipt.receipt.gasUsed);
    let owner_expected = owner_before + contract_before - gas;
    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);

Unfortunately, it still fails. I put together a little calculation:

owner:       165141380959990000000 
+ withdrawl:   1000000000000000000 
- gas:                       19508 
expected:    166141380959989960000 
actually:    166140990799990000000 
diff:             -390159999959040

The gas is really little. Might be that i converted something wrongly.
Any insight appreciated.

Hello @mawize

You have to multiply the gas price by the number of gwei you will use for the transaction.
if you want the transaction to be validate super fast you can pay 20 gwei, otherwise you can just pay 1 gwei but it will be realllyyyy slow. I don’t know the default gwei value for the mocca test suite.

https://eth-converter.com/
|Wei|| | 390159999959040
| — | — |
|Gwei| | 390159.99995904

390159.99995904 / 19508 = 19.999948739

20 gwei makes sense :money_mouth_face:
You can use this site to calculate the cost.

And also i set the option Advanced gas controls in metamask settings.
It could be nice if you want to test reentry exploit.

3 Likes

Owner Test Assignment
Testing the onlyOwner function modifier

edited a few times as I thought it through.

contract("People", async (accounts) => {
  const [owner, alice, bob] = accounts;
  let contractInstance;

  beforeEach(async () => {
    contractInstance = await People.deployed();
  });

  context("when deleting a person", async () => {
    it("shouldn't delete a person without being the contract owner", async () => {
      await contractInstance.createPerson("Alice", 30, 42, {from: alice, value: web3.utils.toWei("1", "ether")});
      await truffleAssert.fails(contractInstance.deletePerson(alice, {from: alice}), truffleAssert.ErrorType.REVERT);
    });

    it("should allow the contract owner to delete a person", async () => {
      await truffleAssert.passes(contractInstance.deletePerson(alice, {from: owner}), truffleAssert.ErrorType.REVERT);
    });
  });
});
1 Like

Value Test Assignment
Testing the withdrawAll function and balance incrementation on createPerson().

If there are any experts I would love some clarification on how to handle gas costs when testing. Cheers!

contract("People", async (accounts) => {
  let contractInstance;
  let [owner, alice, bob] = accounts;

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

  context("when creating a person", async () => {
    it("should add the transaction value to the contract address balance when a person is added", async () => {
      let balance = await web3.eth.getBalance(People.address);
      await contractInstance.createPerson("Alice", 65, 60, {from: alice, value: web3.utils.toWei("1", "ether")});
      let newBalance = await web3.eth.getBalance(People.address);
      expect(parseInt(newBalance)).to.equal(parseInt(balance) + parseInt(web3.utils.toWei("1", "ether")));
    });
  });

  context("when withdrawing the balance of the contract address", async () => {
    it("shouldn't allow anyone but the contract owner to withdraw the contract balance", async () => {
      await contractInstance.createPerson("Alice", 65, 60, {from: alice, value: web3.utils.toWei("1", "ether")});
      await truffleAssert.fails(contractInstance.withdrawAll({from: alice}), truffleAssert.ErrorType.REVERT);
    });

    it("should allow the contract owner to withdraw the contract balance", async () => {
      await contractInstance.createPerson("Alice", 65, 60, {from: alice, value: web3.utils.toWei("1", "ether")});
      let contractBalance = await web3.eth.getBalance(People.address);
      let ownerBalance = await web3.eth.getBalance(owner);
      await contractInstance.withdrawAll({from: owner});
      let newOwnerBalance = await web3.eth.getBalance(owner);

      // this test doesn't take into account transaction gas costs so
      // newOwnerBalance will never be exactly the sum of ownerBalance + contractBalance
      // so we check for less than to let the test pass
      assert(parseInt(newOwnerBalance) <= (parseInt(ownerBalance) + parseInt(contractBalance)));
      expect(parseInt(await web3.eth.getBalance(People.address))).to.deep.equal(0);
    });
  });
});
1 Like

You could look at the discussion above with @gabba and @mawize. But I would not be too concerned with getting the exact right balance calculated in wei. Unless it’s critical to your application I would round off to ether.

1 Like
it("should only let the owner delete a person", async function() {
    let instance = await People.deployed();
    // create person
    await instance.createPerson("Creator", 65, 190, {value: web3.utils.toWei("1", "ether"), from: accounts[1]});
    // check if person exists
    let result = await instance.getPerson({from: accounts[1]});
    assert(result.name === "Creator", "Name not Creator: " + result.name);
    // try to delete person, from same account, should fail
    await truffleAssert.fails(instance.deletePerson(accounts[1], {from: accounts[1]}));
    // delete person from owner account
    instance.deletePerson(accounts[1], {from: accounts[0]})        
    // check if person is deleted
    result = await instance.getPerson({from: accounts[1]});        
    assert(result.name === "", "Person should be deleted");
});
1 Like
let instance;

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

it("should increase the balance of the contract after creating a person", async function() {
    let paymentAmount = Number(web3.utils.toWei("1", "ether"));
    let balanceBefore = Number(await instance.balance());        
    await instance.createPerson("Bob", 65, 190, {value: paymentAmount});
    let balanceAfter = Number(await instance.balance());
    assert(balanceAfter === (balanceBefore + paymentAmount));
});

it("should have the same balance as on the blockchain",async function() {
    let contractBalance = Number(await instance.balance());      
    let blockchainBalance = Number(await web3.eth.getBalance(instance.address));
    assert(contractBalance === blockchainBalance);
});

it("should let the owner withdrawl",async function() {
    await instance.createPerson("Bob", 65, 190, {value: web3.utils.toWei("1", "ether")});
    let contractBalanceBefore = Number(await web3.eth.getBalance(instance.address));
    let ownerBalanceBefore = Number(await web3.eth.getBalance(accounts[0]));
    await truffleAssert.passes(instance.withdrawAll());
    let contractBalanceAfter = Number(await web3.eth.getBalance(instance.address));
    let ownerBalanceAfter = Number(await web3.eth.getBalance(accounts[0]));
    assert(contractBalanceBefore > 0, "Contract balance should be more then 0 to start with");
    assert(contractBalanceAfter === 0, "Contract balance should b 0 after withdrawl");
    // can't get this to test because of fees
    //assert(ownerBalanceAfter === (ownerBalanceBefore + contractBalanceBefore));
    assert(ownerBalanceAfter > ownerBalanceBefore);
});  
1 Like

I figured out solution for handling gas costs. Couldn’t edit my previous post so i’m posting the solution here. Good luck and hope this helps someone.

it("should allow the contract owner to withdraw the contract balance and set contract balance to 0", async () => {
      // set gas price based on Ganache default
      const gasPrice = 20000000000;

      // do the function
      await contractInstance.createPerson("Alice", 65, 60, {from: alice, value: web3.utils.toWei("1", "ether"), gasPrice: gasPrice});
      let contractBalance = parseFloat(await web3.eth.getBalance(People.address));
      let ownerBalance = parseFloat(await web3.eth.getBalance(owner));
      let withdrawal = await contractInstance.withdrawAll({from: owner});
      let gasUsed = withdrawal.receipt.gasUsed;
      let newOwnerBalance = parseFloat(await web3.eth.getBalance(owner));

      // evaluate the function results
      let math = (ownerBalance + contractBalance) - (gasPrice * gasUsed);
      console.log(`1: ${math}`);
      console.log(`2: ${newOwnerBalance}`);

      // finish the assert
      assert(newOwnerBalance === math);
      expect(parseInt(await web3.eth.getBalance(People.address))).to.deep.equal(0);
    });
1 Like

This is my Delete person function:

function deletePerson (address creator) public { 
    require (msg.sender == owner); 
    delete(people[creator]); 
}

it(“Should pass because I am calling the function using the owner”, async function(){

let instance = await People.deployed();
await instance.createPerson("Daniele",20,180, {value: web3.utils.toWei("1", "ether")});
truffleAssert.passes(instance.deletePerson(accounts[1],{from: accounts[0]}));

})

1 Like

Took me 3 hours, 1 bag of dorritos and half liter od cola to figure out that I needed .call lmao.

All works though :slight_smile:

Contract balance should equal variable balance

it(“Contract balance should equal variable balance”, async function(){

await instance.createPerson("Daniele",25,180, {value: 100000});
let balanceFromVariable = await instance.getBalance.call();
let balanceFromBlockChain = await (web3.eth.getBalance(instance.address));

truffleAssert.passes(balanceFromVariable == balanceFromBlockChain);

console.log("Balance from variabile :"  +  balanceFromVariable); //Printed out for sake of testing;
console.log("Balance from blockchain :"  +  balanceFromBlockChain); //Printed out for sake of testing;

});

Should not allow to withdrawal funds

it(“Should not allow to withdrawal funds”, async function(){

truffleAssert.fails(instance.withdrawalAll({from: accounts[5]}));

});

Should allow to withdrawal funds + balance in variable and contract should be 0

it(“Should allow to withdrawal funds + balance in variable and contract should be 0”, async function(){

let instance = await People.new();
let balanceFromVariable = await instance.getBalance.call();
let balanceFromBlockChain = await (web3.eth.getBalance(instance.address));

truffleAssert.passes(instance.withdrawalAll({from: accounts[0]}) && balanceFromVariable == 0 && balanceFromVariable == balanceFromBlockChain );
console.log("Balance from variabile :"  +  balanceFromVariable);
console.log("Balance from blockchain :"  +  balanceFromBlockChain);

})

1 Like

Value Test Assingment:

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

    it("balances should be 0 and shouldn't allow balance not equal to blockchain balance", async function(){

  let instance = await People.new ();
  let balance = await web3.eth.getBalance(instance.address);
  let innerBalance = await instance.balance();
  assert (balance==(innerBalance)&& balance == 0, "not Equal or not = 0");
});


it("shouldn't allow inner balance not equal to blockchain balance after adding a person", async function(){
  //let instance = await People.new ();
  await instance.createPerson("Ben", 30, 210, {value: web3.utils.toWei("1","ether"), from: accounts[3]});
  let balance = await web3.eth.getBalance(instance.address);
  let innerBalance = await instance.balance();
  assert (balance > 0 && balance ==(innerBalance), "blockchain balance not > 0 or not Equal to balance");
});

it(" If you are the Owner you can withdraw money", async function(){

     let instance = await People.new ();
     await instance.createPerson("Ben", 30, 210, {value: web3.utils.toWei("1","ether"), from: accounts[8]});
     await instance.createPerson("Jeff", 30, 210, {value: web3.utils.toWei("1","ether"), from: accounts[8]});
      await truffleAssert.passes(instance.withdrawAll({from: accounts[0]}),truffleAssert.ErrorType.REVERT);
    });

    it(" If you are not the Owner you can not withdraw money", async function(){
         let instance = await People.new();
         await instance.createPerson("Ben", 30, 210, {value: web3.utils.toWei("1","ether"), from: accounts[8]});
         await truffleAssert.fails(instance.withdrawAll({from: accounts[8]}),truffleAssert.ErrorType.REVERT);
      });

    it(" the Owner balance should increase after withdrawl and contractBalance ==0 ", async function(){
           let instance = await People.new();
            await instance.createPerson("Ben", 30, 210, {value: web3.utils.toWei("1","ether"), from: accounts[9]});
            let balanceZero = await web3.eth.getBalance(accounts[0]);
           await instance.withdrawAll();
           let balanceOne = await web3.eth.getBalance(accounts[0]);
           let contractBalance = await web3.eth.getBalance(instance.address);
          assert (balanceOne > balanceZero && contractBalance == 0, "acount zero balance did not increase or contractBalance != 0 ");
        });


          it(" contratAccount should be 0 after withdrawl and equal to blockchainBalance", async function(){
                 let instance = await People.new();
                await instance.createPerson("Ben", 30, 210, {value: web3.utils.toWei("1","ether"), from: accounts[9]});
                await instance.withdrawAll();
                  let blockchainBalance = await web3.eth.getBalance(instance.address);
                 let balance = await instance.balance();
                assert (balance == blockchainBalance && balance == 0 , "balance not equal to ChainBalance and not zero")
            });


});
1 Like

Hi Filip

  1. as part of my google research I updated the people migration file in order to get the contract address as follows:
const People = artifacts.require("People");
module.exports = function(deployer, network, accounts) {
  deployer.deploy(People)
   .then(function() { console.log("CONTRACT ADDRESS IS "+People.address)})

So I could see the address numerically in every test running. I noticed that in every test the contract address is different.
is it because it changes in every deployment (not only new?)?

  1. I didn’t use the parseFloat in my code. but it worked…
    Beginners’ luck or my code is totally out of focus so it doesn’t matter…?

Thanks :slight_smile:

I tested four combinations of {owner, client, other client}

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

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

 it("should allow the contract owner to delete themselves.", async function(){
    let instance = await People.new();
    await instance.createPerson("Kamakazi", 14, 160, {from: accounts[0], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.passes(instance.deletePerson(accounts[0], {from: accounts[0]}));
  });
  it("should allow the contract owner to delete somebody else.", async function(){
    let instance = await People.new();
    await instance.createPerson("Hilliary", 76, 160, {from: accounts[0], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.passes(instance.deletePerson(accounts[1], {from: accounts[0]}));
  });
  it("should not allow the same non-contract owner to delete themselves.", async function() {
    let instance = await People.new();
    await instance.createPerson("RobinWilliams", 29, 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 not allow a different non-contract owner to delete someone else.", async function() {
    let instance = await People.new();
    await instance.createPerson("Hinkle", 29, 160, {from: accounts[2], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.fails(instance.deletePerson(accounts[3], {from: accounts[2]}), truffleAssert.ErrorType.REVERT);
  });
});
1 Like

Great! Yeah the address changes every time it deploys.

I think the test will work fine without parsefloat as long as eth.getBalance is zero. But otherwise I don’t think the comparison will work. But in that case the test should fail either way.

But it’s a good habit for future tests to make sure that the units that you are comparing are the same. You can read up on floats vs integers if you need more information :slight_smile:

I will . Thank you:)

I made inequality allowances for gas charges on passing ETH around, I should look into how that’s done. I figured the values wouldn’t be exact due to gas friction, which I’ve no idea how to pre or post calculate yet. But upon playing with it I see the transferred values are exact. Seems Truffle is adding in the transaction fee without modifying the sent values. And that makes sense too in its own way.

Also had to keep account balances in mind when re-using accounts to create 2nd and 3rd Persons in the same test run. I wouldn’t expect them to reset. Its clear that they don’t in Ganache (which is French so in English it’s pronounced “gan-nahsh”, silent e.) between test cases, but do between test suite runs.

Mine seems to run and completely pass with simpler code (less currency conversions) than I see in your solution, so I am suspicious of some of the comparisons. I’ll dive deeper into that later.

it("should increase balance when a person is added by owner.", async function() {
    await instance.createPerson("Narcasist", 29, 160, {from: accounts[0], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.passes(web3.eth.getBalance(instance.address) >= web3.utils.toWei("0.5", "ether"));
  });
  it("should decrease owner's balance when a person is added by owner.", async function() {
    await instance.createPerson("Narcasist", 29, 160, {from: accounts[0], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.passes(web3.eth.getBalance(accounts[0]) < web3.utils.toWei("98", "ether"));
  });
  it("should increase balance when a person is added by a non-owner.", async function() {
    await instance.createPerson("Shawn", 29, 160, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.passes(web3.eth.getBalance(instance.address) >= web3.utils.toWei("1", "ether"));
  });
  it("should decrease originating balance when a person is added by a non-owner.", async function() {
    await instance.createPerson("Sheep", 29, 160, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.passes(web3.eth.getBalance(accounts[1]) <= web3.utils.toWei("98", "ether"));
  });
  it("internal balance variable should match on-chain contract balance.", async function() {
    await instance.createPerson("Smith", 29, 160, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
    let balance = await instance.balance();
    await truffleAssert.passes(web3.eth.getBalance(instance.address) == balance);
  });
  it("should increase balance by 1 Eth when a person is added.", async function() {
    await truffleAssert.passes(web3.eth.getBalance(instance.address) == web3.utils.toWei("0", "ether"));
    await instance.createPerson("Jones", 29, 160, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.passes(web3.eth.getBalance(instance.address) == web3.utils.toWei("1", "ether"));
  });
  it("should increase balance by 2 Eth when two people are added.", async function() {
    await truffleAssert.passes(web3.eth.getBalance(instance.address) == web3.utils.toWei("0", "ether"));
    await instance.createPerson("Jones", 29, 160, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.passes(web3.eth.getBalance(instance.address) == web3.utils.toWei("1", "ether"));
    await instance.createPerson("JonesToo", 29, 160, {from: accounts[2], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.passes(web3.eth.getBalance(instance.address) == web3.utils.toWei("2", "ether"));
  });
  it("should allow contract owner to withdraw all of its balance.", async function() {
    await instance.createPerson("Jones", 29, 160, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
    await instance.createPerson("JonesToo", 29, 160, {from: accounts[2], value: web3.utils.toWei("1", "ether")});
    await instance.createPerson("OtherJones", 29, 160, {from: accounts[3], value: web3.utils.toWei("1", "ether")});
    await instance.createPerson("JJonesAnon", 29, 160, {from: accounts[4], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.passes(instance.withdrawAll({from: accounts[0]}));
  });
  it("should increase contract owner's balance by same amount on full withdraw.", async function() {
    await instance.createPerson("Jones", 29, 160, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
    await instance.createPerson("JonesToo", 29, 160, {from: accounts[2], value: web3.utils.toWei("1", "ether")});
    await instance.createPerson("OtherJones", 29, 160, {from: accounts[3], value: web3.utils.toWei("1", "ether")});
    await instance.createPerson("JJonesAnon", 29, 160, {from: accounts[4], value: web3.utils.toWei("1", "ether")});
    await instance.withdrawAll({from: accounts[0]});
    await truffleAssert.passes(web3.eth.getBalance(accounts[0]) >= web3.utils.toWei("103", "ether"));
  });
  it("should not allow non-contract owners to withdraw all of its balance.", async function() {
    await instance.createPerson("Thief", 29, 160, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
    await instance.createPerson("Ericson", 29, 160, {from: accounts[2], value: web3.utils.toWei("1", "ether")});
    await truffleAssert.fails(instance.withdrawAll({from: accounts[1]}));
    await truffleAssert.fails(instance.withdrawAll({from: accounts[2]}));
    await truffleAssert.fails(instance.withdrawAll({from: accounts[3]}));
  });
1 Like

OWNER test solution:

it("non-owner should not be able to delete person.",
  async function(){
    let instance = await People.deployed();
    // Create person using account[1].
    await instance.createPerson("Clem", 25, 160, {value: web3.utils.toWei("1", "ether"), from: accounts[1]});
    // Assert that accounts[1] cannot delete the person.
    await truffleAssert.fails(instance.deletePerson(accounts[1],{from: accounts[1]}), truffleAssert.ErrorType.REVERT);
  });

it("owner should be able to delete person.",
  async function(){
    let instance = await People.deployed();
    // Delete the person as accounts[0]
    await instance.deletePerson(accounts[1],{from: accounts[0]});
    // Retrieve person record
    let result = await instance.getPerson({from: accounts[1]});
    // Age should now be 0 and name should be ""
    assert(result.age.toNumber() === 0 &&
           result.name === "", "Person was not deleted.");
  });
1 Like

Unit Testing Values:
@filip, Tried to do an audit after the withdraw by calculating gas usage and tally that to the owner’s original value plus the amount paid. Even with BigNumber math I had to round the values up (choose gwei) due to an error encountered when using wei (something about processing a value greater than 53 bytes). This solution works but there is a possibility of a false negative due to rounding. Any suggestions? Thank you - David

it(“should increase balance”, async function(){
// Reset contract
let instance = await People.new();
let amt = web3.utils.toWei(“1”, “ether”);
// Add person using account 1
await instance.createPerson(“Clem”, 25, 160, {value: amt, from: accounts[1]});
// Get contract balance varialbe amount.
let contractInternalBalance;
await instance.balance.call(function(err, res){
contractInternalBalance = res;
});
// Get contract balance
let contractBalance = await web3.eth.getBalance(instance.address);
// Contract internal value should be equal to amt paid
assert(amt === contractInternalBalance, “Contract internal balance should equal amount paid.”);
// Contract internal balance variable value should match contract value.
assert(contractBalance === contractInternalBalance, “Contract internal balance should contract balance.”);
});

it(“should withdraw balance”, async function(){
// Reset contract
let instance = await People.new();
let ownerStartBalance = await web3.eth.getBalance(accounts[0]);
let amt = web3.utils.toWei(“1”, “ether”);
// Add person using account 1
await instance.createPerson(“Clem”, 25, 160, {value: amt, from: accounts[1]});
// Get contract balance
let contractBalance = await web3.eth.getBalance(instance.address);
// Make sure that the amount paid was recorded.
assert(contractBalance === amt, “Contract balance should equal amount paid.”);
// Withdraw Balance, capture txInfo.
const txInfo = await instance.withdrawAll({from: accounts[0]});
// Get gas used
const tx = await web3.eth.getTransaction(txInfo.tx);
// Calculate gas cost.
let gasCost = new BN(tx.gasPrice, 10).mul(new BN(txInfo.receipt.gasUsed, 10));
// Get contract balance
contractBalance = await web3.eth.getBalance(instance.address);
// Account balanace should now be 0
assert(contractBalance == 0, “Contract balanace should be 0.”);
// Get owner’s current balanace.
let ownerAmt = await web3.eth.getBalance(accounts[0]);
// Convert all values to gwei to avaid overflow error
ownerAmt = parseInt(web3.utils.fromWei(ownerAmt, “gwei”));
ownerStartBalance = parseInt(web3.utils.fromWei(ownerStartBalance, “gwei”));
amt = parseInt(web3.utils.fromWei(amt, “gwei”));
gasCost = parseInt(web3.utils.fromWei(gasCost, “gwei”));
// Calculate target amount
let targetAmt = new BN(ownerStartBalance, 10).add(new BN(amt, 10)).sub(new BN(gasCost, 10));
// Owner amount should equal ownerStartAmount + amt
// Due to rounding this check could give false negatives.
assert(ownerAmt == targetAmt.toNumber(), "Owner amount should now equal " + targetAmt + " but is " + ownerAmt);
});

Could you give some examples of the different numbers your getting? How large are the rounding errors?