0

I am trying to test my contracts using the Truffle suite, along with Ganache.

Up until now, I have been testing contract functions which are restricted to view only. Now, when I attempt to make changes to the contract state, I'm finding that the state is not actually being changed between function calls.

Consider the following simple contract:

pragma solidity ^0.4.24;

import "../OrderBook.sol";

contract IdManager {
    uint256 newId;

    function addId() public returns (uint256) {
        newId++;
        return newId;
    }
}

All I wish to do is increment newId and return its new value.

The code I have written to test this is:

const { getWeb3, getContractInstance, parseSignature } = require("./test_helper");
const web3 = getWeb3();
const getInstance = getContractInstance(web3);

contract('IdManager', (accounts) => {
    let IdManager = getInstance('IdManager');

    it('test addId()', async () => {
        console.log(await IdManager.methods.addId().call());
        console.log(await IdManager.methods.addId().call());
        console.log(await IdManager.methods.addId().call());
    });
});

The getInstance() call is making use of Web3 v1.0, based off this tutorial.

With the above test, I would expect the output to be:

1
2
3

But, I actually get:

1
1
1

Has anyone come across this issue before?


Edit0:

console.log(await IdManager.methods.newId().call()); should have been console.log(await IdManager.methods.addId().call());newId() is now addId().


Edit1:

Taking goodvibration's advice, I have modified the example contract to be as follows:

contract IdManager {
    uint256 public newId;

    function addId() public {
        newId++;
    }

    function getId() public view returns (uint256) {
        return newId;
    }
}

The test code has been altered to the following:

it('test addId()', async () => {
    await IdManager.methods.addId().call();
    console.log(await IdManager.methods.getId().call());

    await IdManager.methods.addId().call();
    console.log(await IdManager.methods.getId().call());

    await IdManager.methods.addId().call();
    console.log(await IdManager.methods.getId().call());
});

Unfortunately, my output is still showing that the state is not being preserved:

0
0
0
4
  • You're not calling addId anywhere in your test!!!!! Commented Sep 17, 2018 at 14:40
  • I have updated my question, newId() should have been addId(). Apologies.
    – Craig
    Commented Sep 17, 2018 at 14:45
  • What is OrderBook??? Is that possibly the old contract which you were trying to run this test with? Commented Sep 17, 2018 at 15:03
  • Sorry, I'm not having a very good day. I'm pulling from the wrong set of tests there! I have updated my question again.
    – Craig
    Commented Sep 17, 2018 at 15:05

3 Answers 3

4

A simple solution to this was right under my nose. Obviously, I just had to read the documentation given to me.

methods.myMethod.call

Note calling can not alter the smart contract state.

methods.myMethod.send

Note this can alter the smart contract state.

So, the alteration to the last passage of test code in my question is as follows:

it('test addId()', async () => {
    await IdManager.methods.addId().send({from: accounts[0]});
    console.log(await IdManager.methods.getId().call());

    await IdManager.methods.addId().send({from: accounts[0]});
    console.log(await IdManager.methods.getId().call());

    await IdManager.methods.addId().send({from: accounts[0]});
    console.log(await IdManager.methods.getId().call());
});

Which results in my desired output:

1
2
3
0

Function addId is a non-constant function (i.e., not declared as pure or view).

As such, its return-value can be used only in the on-chain, i.e., by other functions (in the same contract or in other contracts) which call it.

When you call such function from the off-chain, then a transaction is executed in the on-chain, and what you get in return is a receipt of that transaction.

If you want the actual return-value, then you need to emit it in an event, which you will later be able to extract from the receipt.

For example:

event Event(uint256 val);
function addId() public returns (uint256) {
    newId++;
    emit Event(newId);
    return newId;
}

Alternatively, since in your specific case you are returning the value of a global (state) variable, you can declare it public, and then simply read it from the contract using its (implicit) getter function:

console.log(await IdManager.methods.newId().call());
1
  • I should have remembered that executing such functions only returns the Tx receipt, from when I was solely using Web3 and remix! But, I have altered my test code to retrieve the member Id via a separate function call. I am still not seeing a change in state between calls.
    – Craig
    Commented Sep 17, 2018 at 15:01
0

As of December 2021, Truffle has changed the way contract should be called. This question is extremely important for someone as inattentive as I am so here's a sample test for the contract from Edit1

const idmanager= artifacts.require("IdManager"); 

contract('IdManager', async accounts => {

    it('test addId()', async () => {
        const instance = idmanager.deployed(); 

        await instance.addId({from: account[0]}); 
        const actual = await instance.getId.call({from: account[0]});

        assert.equal(actual, 1); 
    });
});

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.