Unit Testing in Truffle

@filip - Looks like this was a bone-headed mistake on my part. Here was the output without converting to gwei:

AS WEI
owner start bal: 97892192780000000000
amt paid: 1000000000000000000
gas cost: 390160000000000
owner end bal: 98891802620000000000
1) should withdraw balance

  1. Contract: People
    should withdraw balance:
    Error: Number can only safely store up to 53 bits
    at assert (node_modules\bn.js\lib\bn.js:6:21)

So the issue is that I had converted everything to BigNumbers but was still trying to convert the targetAmt to a number

assert(ownerAmt == targetAmt.toNumber(), "Owner …

Once I removed the toNumber() call, this test passed fine using WEI values. So the complete working test that includes capturing the amount spent in gas is:

  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]);
    // 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, "Owner amount should now equal " + targetAmt + " but is " + ownerAmt);
  });
1 Like

Hi @xactant

Here is a small test i wrote to show how to test the gas fee.
I was having the same issues than you i hope it will help

  it("...[simple withdrawBankAccount] The value withdraw should match with the contract balance minus gas fee", async () => {
    const accountBalanceBeforeTransaction = await web3.eth.getBalance(accounts[0]);
    const contractBalanceBeforeTransaction = await web3.eth.getBalance(ContractInstance.address);

    let result = await ContractInstance.withdrawBankAccount();

    let accountBalanceAfterTransaction = await web3.eth.getBalance(accounts[0]);
    const contractBalanceAfterTransaction = await web3.eth.getBalance(ContractInstance.address);
    
    // this is the amount of gas used in for the transaction
    let tx_info = await web3.eth.getTransaction(result.tx);
    let gasPrice = tx_info.gasPrice; // 20000000000
    let finalGasPrice = result.receipt.gasUsed * gasPrice;

    /*
    * Here we are adding the value hold by the contract before the transaction
    * To the old account Balance 
    * Minus the final Gas price
    */
    const accountValueExpected = parseInt(accountBalanceBeforeTransaction) +
                (parseInt((contractBalanceBeforeTransaction)-parseInt((contractBalanceAfterTransaction))))
                - parseInt(finalGasPrice);

    assert.equal(parseInt(accountBalanceAfterTransaction).toString(), accountValueExpected.toString(),
          "The value of the wallet should be equal to the difference between the old and the new contract value.");

  });

Edit: no screenshot needed :slight_smile:

4 Likes

Oh you fixed it
:partying_face:

2 Likes

@gabba Thank you for responding though :slight_smile:

1 Like

Hi @filip
I am stuck at lesson " Testing for Errors"
When I am testing the peopletest.js file in truffle I get this error message:

TypeError [ERR_INVALID_REPL_INPUT]: Listeners for uncaughtException cannot be used in the REPL

Do you have a solution for this? I tried to google but have no solution yet…

This here should be a solution but I dont know hot to apply it…

1 Like

Hi.
What operating system are you using?

You should look for the file called: runner.js
The file is stored in the npm folder: node_modules/truffle/node_modules/mocha/lib/runner.js

when you locate the file runner.js, open the file in your editor and delete lines: 867, 868 and 869

Then just save and try running your tests again.

If you still are having problems, then look at this post, and try the solution suggested by @mawise

Good luck and tell me if you are still having problems.

Ivo

2 Likes

Hi,
thanks!
I use windows 10. And I did not find a mocha folder…

I tried reinstall the following

npm un -g truffle
npm i -g truffle@nodeLTS

But then this error occured…

npm ERR! code ENOENT
npm ERR! syscall spawn git
npm ERR! path git
npm ERR! errno ENOENT
npm ERR! enoent Error while executing:
npm ERR! enoent undefined ls-remote -h -t https://github.com/trufflesuite/mocha.git
npm ERR! enoent
npm ERR! enoent
npm ERR! enoent spawn git ENOENT
npm ERR! enoent This is related to npm not being able to find a file.
npm ERR! enoent

npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\Chef\AppData\Roaming\npm-cache_logs\2020-02-03T17_03_32_266Z-debug.log

Hi @jimmy2k
When you are using -g with npm you are installing the package globally , not in your local repository.
If it’s what you want you should find where the folder is there:

npm config get prefix

This command will give you the folder where it is installed.
Usually i install packages locally, like that you have only the packages related to your application on the node_modules folder.

Your error is maybe caused by your npm version, i m using

npm --version
6.9.0

And i was able to install it locally try to update npm with

npm update

If you are using the same version than mine, btw i am on linux it’s maybe an other error

2 Likes

the location of the file is written in the rest of the first error code you pasted.

TypeError [ERR_INVALID_REPL_INPUT]: Listeners for  `uncaughtException`  cannot be used in the REPL

if you run it again and paste the entire error code, maybe I can help you locate the runner.js file. :thinking:
I think it is inside a hidden folder called .node_modules/etc etc… I`ll try to locate the file on my computer…

Ivo

1 Like

I located the file on my computer.

C:\Users\Ivo\AppData\Roaming\npm\node_modules\truffle\node_modules\mocha\lib

To find it with file explorer you will have to turn on the checkbox for ‘Show hidden files’.
Then you will see the AppData folder inside your user folder.

I see that your user name is Chef?
If that`s the case, you can try to type this in run: (right-click on start menu button).

C:\Users\Chef\AppData\Roaming\npm\node_modules\truffle\node_modules\mocha\lib

I`ll attach some pictures of how iI located the file.
image
image
image

If you don’t locate the file now, I would consider buying a Macbook instead. :sweat_smile: :sweat_smile:

Ivo

2 Likes

thanks ivo! I have tried deleting the lines in runner.js…

but it did not help…looking for other solutions…

maybe I have to many mocha folder installed already? I searched “mocha” in my c:/ device and there are 2 “mocha” folders found:

  1. in
    C:\Users\Chef\Documents\ether
    eumcourse\node_modules
  2. in
    C:\Users\Chef\AppData\Roaming\npm\node_modules\truffle\node_modules

Hi.
Don’t worry about the 2 mocha folders, that`s not the issue. :wink:

  1. Is local to your Ethereum course folder.
  2. Is the folder that contains the runner.js and it is a part of the Nodejs/npm program.

I’m not going up, so I have a couple of questions.
What console are you running the tests with?

  • Power Shell or Cmdr?

What error message do you get if you run it now?

  • The first error was related to mocha and the runner.js file.
  • The second error seems to be about the local and global issues…

I would have done this:

  1. Try to run the tests on another machine. Remember to change runner.js on the new machine before you run the tests.
  2. Post the entire error message so we see what it says? Screenshots are ok… :slight_smile:
  3. Share the project to GitHub or Google Drive so I can download it and test it.

Ivo

1 Like

Hey,
finally I found a solution :slight_smile:

I have unistalled node12 and installed
node 10.18.1
afterwards everything went well! maaaan, that took me quite a while. Do you think it will be alright for me to move on with than node version?
Thanks again!

1 Like

Congrats… Just try it and tell me if it works… :ok_hand:
Ivo

2 Likes

Yes I think you will be fine!

2 Likes

The Owner Test assignment:

  it("shouldn't delete a person when not the owner calls", async function() {
    const instance = await People.deployed()
    const createFromAddress = accounts[1]
    const deleteFromAddress = accounts[1]

    await instance.createPerson('Bob', 65, 180, { value: web3.utils.toWei('1', 'ether'), from: createFromAddress })

    // Fails to delete
    await truffleAssert.fails(
      instance.deletePerson(createFromAddress, { from: deleteFromAddress }),
      truffleAssert.ErrorType.REVERT
    )

    // Person should still exist
    const notDeletedPerson = await instance.getPerson({ from: createFromAddress })
    assert(notDeletedPerson.name === 'Bob')
  })

  it('should delete a person when the owner calls', async function() {
    const instance = await People.deployed()
    const createFromAddress = accounts[1]
    const deleteFromAddress = accounts[0] // Owner

    // Succeeds to delete
    await instance.createPerson('Bob', 65, 180, { value: web3.utils.toWei('1', 'ether'), from: createFromAddress })

    // Person should not exist
    await truffleAssert.passes(instance.deletePerson(createFromAddress, { from: deleteFromAddress }))
    const deletedPerson = await instance.getPerson({ from: createFromAddress })
    assert(deletedPerson.name === '')
  })
1 Like

@filip It is correct that a deleted person still returns an ‘empty’ result right? With the name being “”, the age 0, height 0 etc?

Is this normal behaviour in smart contracts or would you advise some error checking in the getPerson call? Like to check if the person exists, if so return it, otherwise throw an error.

1 Like

The Value Assignment:

 const GAS_PRICE = 20000000000

 it('should add balance when creating a person', async function() {
    // Setup
    const valueToSend = web3.utils.toWei('1', 'ether')
    const contractAddress = instance.address

    // Act
    await truffleAssert.passes(instance.createPerson('Bob', 65, 180, { value: valueToSend }))

    // Assert
    const localBalance = await instance.balance()
    const contractBalance = await web3.eth.getBalance(contractAddress)

    assert.equal(localBalance, valueToSend)
    assert.equal(contractBalance, valueToSend)
  })

  it('should withdraw the balance by owner', async function() {
    // Setup
    const addedValue = web3.utils.toWei('1', 'ether')
    await instance.createPerson('Bob', 65, 180, { value: addedValue, from: accounts[1] }) // add funds to the contract
    const initialAccountBalance = await web3.eth.getBalance(accounts[0])
    const contractAddress = instance.address

    // Act
    const withdrawal = await instance.withdrawAll()

    // Assert
    const expectedAccountBalance = initialAccountBalance - withdrawal.receipt.gasUsed * GAS_PRICE + +addedValue
    const newAccountBalance = await web3.eth.getBalance(accounts[0])
    assert.equal(newAccountBalance, expectedAccountBalance)

    const localBalance = await instance.balance({ from: accounts[1] })
    assert.equal(localBalance, 0)

    const contractBalance = await web3.eth.getBalance(contractAddress)
    assert.equal(contractBalance, 0)
  })

It was a bit tricky to figure out how much the balance of the owner should be after the withdrawal. But I could find the gasUsed in the receipt and noticed the gas price on Ganache is 20000000000.

1 Like

Yes, all “empty” values in a mapping will be a zero equivalent of its data structure. So if the value is of type int all “empty” values or uninitialized values will be 0. If it’s a string it will be an empty string. There is no undefined state or null state for values in a mapping.

So let’s say I initialize a mapping test(address -> uint). If I directly after, without any other initialization, look up test(MY_ADDRESS) it will return 0, even though I haven’t set it to 0. It’s a default. And if your value is of type Struct, then all values of that struct would be of zero-equivalents.

1 Like
it("should be the creator of the contract", async function(){
    let instance = await People.deployed();        
    let result = await instance.getCreator(0);
    assert(result === accounts[0], "Is not the creator");
});
it("shouldn't delete a person if is not the contract owner", async function(){
    let instance = await People.deployed();   
    await instance.createPerson("Nando", 45, 182, { value: web3.utils.toWei("1", "ether"), from: accounts[1] });
    await truffleAssert.fails(instance.deletePerson(accounts[1], {from: accounts[1]}), truffleAssert.ErrorType.REVERT);
});
1 Like