Truffle – Testing Promises That Throw Errors in testrpc

testingtestrpctruffle-contract

I have contract with methods that check input parameters using require(bool) so if at least on of conditions does not matched it throws an error. During test procedure (using Truffle and Chaijs) it looks like:

 Error: VM Exception while processing transaction: invalid opcode
  at Object.InvalidResponse (/usr/lib/node_modules/truffle/build/cli.bundled.js:37022:16)
  at /usr/lib/node_modules/truffle/build/cli.bundled.js:210096:36
  at XMLHttpRequest.request.onreadystatechange (/usr/lib/node_modules/truffle/build/cli.bundled.js:208875:13)
  at XMLHttpRequestEventTarget.dispatchEvent (/usr/lib/node_modules/truffle/build/cli.bundled.js:210748:18)
  at XMLHttpRequest._setReadyState (/usr/lib/node_modules/truffle/build/cli.bundled.js:211038:12)
  at XMLHttpRequest._onHttpResponseEnd (/usr/lib/node_modules/truffle/build/cli.bundled.js:211193:12)
  at IncomingMessage.<anonymous> (/usr/lib/node_modules/truffle/build/cli.bundled.js:211153:24)
  at endReadableNT (_stream_readable.js:1047:12)
  at _combinedTickCallback (internal/process/next_tick.js:102:11)
  at process._tickCallback (internal/process/next_tick.js:161:9)

Test uses promises like this:

return MyToken.deployed().then(inst => {
  instance = inst;
  return instance.purchase({from: accounts[1], value: amount});
})....

Problem is that I cannot handle this exception. I tried to wrap purchase call into function and use assert.throws(executor, message) – it does not works (suppose due it expects something synchronous).

How this promise can be tested (preferably without adding chai-as-promised)?

Best Answer

You can catch a promise when it was rejected before further processing. Catch doesn'y work in the example because it will catch any other error not only the transaction failure.

You can use the second parameter to then to catch a promise being rejected before further processing, ie MyPromise.then(() => { /* check when succeed */ }, () => { /* check when failure */ })

contract('MyToken', (accounts) => {
  it("Should fail", () => {
    return MyToken.deployed().then((instance) => {
      return instance.purchase({ from: accounts[1], value: amount });
    }).then(() => {
      assert.ok(false, "It didn't fail");
    }, () => {
      assert.ok(true, "Passed");
    });
  });
});

Promises do not throws that is why assert.throws fails.

Note after Byzantium: Also I should mention that after the Byzantium fork some operations that previously generated a throw will no longer do such thing. Check this post so you can update your test for that fact: How do I detect a failed transaction after the Byzantium fork as the REVERT opcode does not consume all gas?


Perhaps a better approach is using async/await, and assert.throws.

The trick is convert the promise to something not async that assert.throws can report. Firs a helper function

async function verifyThrows(pred, message) {
  let e;
  try {
    await pred();
  } catch (ex) {
    e = ex;
  }
  assert.throws(() => {
    if (e) { throw e; }
  }, message);
}

And we can use the helper function passing the promise execution in a function

contract('MyToken', (accounts) => {
  it("Should fail", async () => {
    const instance = await MyToken.deployed();
    await verifyThrows(() => {
      instance.purchase({ from: accounts[1], value: amount });
    }, /revert/);
  })
});
Related Topic