Truffle Testing – Testing Contract Function After Transaction

testingtestrpctruffle

I try to test this contract with Truffle but the test of checkGoalReached function Fail and I do not understand why.

  • The code to reproduce this problem is available here
  • With Remix Solidity IDE, the behavior of the contract is ok
  • TestRPC v3.0.5
  • Truffle v3.2.4

The contract

pragma solidity ^0.4.10;

contract CrowdFunding {
    // Defines a new type with two fields.
    struct Funder {
        address addr;
        uint amount;
    }

    struct Campaign {
        address beneficiary;
        uint fundingGoal;
        uint numFunders;
        uint amount;
        mapping (uint => Funder) funders;
    }

    uint numCampaigns;
    mapping (uint => Campaign) campaigns;

    function newCampaign(address beneficiary, uint goal) returns (uint campaignID) {
        campaignID = numCampaigns++; // campaignID is return variable
        // Creates new struct and saves in storage. We leave out the mapping type.
        campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
    }

    function contribute(uint campaignID) payable {
        Campaign c = campaigns[campaignID];
        // Creates a new temporary memory struct, initialised with the given values
        // and copies it over to storage.
        // Note that you can also use Funder(msg.sender, msg.value) to initialise.
        c.funders[c.numFunders++] = Funder(msg.sender, msg.value);
        c.amount += msg.value;
    }

    function checkGoalReached(uint campaignID) returns (bool reached) {
        Campaign c = campaigns[campaignID];
        if (c.amount < c.fundingGoal)
            return false;
        uint amount = c.amount;
        c.amount = 0;
        c.beneficiary.transfer(amount);
        return true;
    }
}

Javascript Tests

var CrowdFunding = artifacts.require("./CrowdFunding.sol");

contract('CrowdFunding', function(accounts) {

      var account_one = accounts[0];
      var account_two = accounts[2];
      var campaign;
      var id;

        it("should create a new campaign", function() {
          return CrowdFunding.deployed().then(function(instance) {
            campaign = instance;
            return campaign.newCampaign.call(account_two, 2)
            .then(function (campaignID) {
              id = campaignID;
              return campaign.contribute(campaignID, {from: account_one, value: 1});
            })
            .then(function() {
              assert.equal(web3.eth.getBalance(campaign.address).toNumber(), 1, "Balance isn't 1 after one contribution of 1");
              return campaign.checkGoalReached.call(id);
            })
            .then(function(reached) {
                assert.equal(reached, false, "Campaign with goal 2 is reached with balance 1");
            });
        });
      });
    });

Truffle test Result

enter image description here

Best Answer

I think you missed something here. In the context of a Blockchain environment, you must consider a method/function which writes data and a method which reads data in a completely different manner.

Read data (call):

That's really easy to read data on the Blockchain, you don't have to go too far, your node already contains the whole blockchain. It's kind of accessing the local storage.

In the Ethereum ecosystem, You will have to write a read-only function that doesn't modify the state (variables).

For example

Solidity

function getCampaignDetails(uint campaignID) constant returns (uint256, uint256) {
    Campaign c = campaigns[campaignID];
    return (c.fundingGoal, c.amount);
}

Web3

CrowdFunding.deployed().then(function(instance) {
    return instance.getDetails.call(id);
 }).then(function(details) {
    console.log(details);
})

I use the keyword constant to enforce the compiler that's a read-only function (optional). And a Web3 call returns a promise that contains the result of the function (see doc).

A call doesn't cost any gas, can't receive ether, is synchronous and the return value of the contract function is returned immediately.

Write data (transaction):

To write data on the blockchain, you have to send a transaction, it's way more complicated because the transaction has to be broadcast on the network, mined, validated and published.

For example

Solidity

function newCampaign(address beneficiary, uint goal)  {
    campaignID = numCampaigns++;
    campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
}

Web3

CrowdFunding.deployed().then(function(instance) {
    return instance.newCampaign(account_two, 2);
}).then(function (transactionHash) {
    console.log(transactionHash); // useful for tracking
})

In this example, the function newCampaign changes the state by adding a new campaign to the map. A transaction is asynchronous, the success of execution depends on the miner so you can't expect any data in return. However, you can send an event to share some data.


So basically, your code can't work because you need this campaignID. I would store the campaign creator and the campaignID in the Campaign struct, build a call function that returns my campaigns (Filter using campaignCreator == msg.sender) and then select a campaign and run your test.

Related Topic