34

How can I catch "revert" error in truffle test written in javascript?

Error: VM Exception while processing transaction: revert

Here is part of test contract,

  function doRevert() public {
    revert();
  }

and here is my part of test javascript,

  it("test1.....", async() => {
    let instant = await myContract.deployed();    

    await instant.doRevert();
.
.
.

I do not want to stop my test code at the first test step. I searched that I can import expectThrow.js, but I couldn't to import it, same issue as here. I know this question is very related to How can you handle an expected throw in a contract test using truffle and ethereum-testRPC?, but still cannot figure it out how to use the library, expectThrow.js, and throw is deprecated, so there is some other ways to do that?

7 Answers 7

51

Answer by example:

Suppose you have a function that can be invoked only by the owner (deployer) of the contract.

If it's invoked by anyone other than the owner, then it reverts (require with a false condition).

File exceptions.js:

module.exports.errTypes = {
    revert            : "revert",
    outOfGas          : "out of gas",
    invalidJump       : "invalid JUMP",
    invalidOpcode     : "invalid opcode",
    stackOverflow     : "stack overflow",
    stackUnderflow    : "stack underflow",
    staticStateChange : "static state change"
}

module.exports.tryCatch = async function(promise, errType) {
    try {
        await promise;
        throw null;
    }
    catch (error) {
        assert(error, "Expected an error but did not get one");
        assert(error.message.startsWith(PREFIX + errType), "Expected an error starting with '" + PREFIX + errType + "' but got '" + error.message + "' instead");
    }
};

const PREFIX = "VM Exception while processing transaction: ";

File MyContractTest.js:

contract("MyContractTest", function(accounts) {
    let myContract;
    let owner    = accounts[0];
    let nonOwner = accounts[1];
    let tryCatch = require("./exceptions.js").tryCatch;
    let errTypes = require("./exceptions.js").errTypes;

    describe("sanity assertion:", function() {
        before(async function() {
            myContract = await artifacts.require("MyContract.sol").new();
        });
        it("should complete successfully", async function() {
            await myContract.func({from: owner});
        });
        it("should abort with an error", async function() {
            await tryCatch(myContract.func({from: nonOwner}), errTypes.revert);
        });
    });
});

UPDATE: Version 2

I would like to update this answer with slightly simpler code. Its behavior should be identical, but it "saves a few characters" in the test itself, thus improves readability and allows easier maintenance.

File exceptions.js:

const PREFIX = "Returned error: VM Exception while processing transaction: ";

async function tryCatch(promise, message) {
    try {
        await promise;
        throw null;
    }
    catch (error) {
        assert(error, "Expected an error but did not get one");
        assert(error.message.startsWith(PREFIX + message), "Expected an error starting with '" + PREFIX + message + "' but got '" + error.message + "' instead");
    }
};

module.exports = {
    catchRevert            : async function(promise) {await tryCatch(promise, "revert"             );},
    catchOutOfGas          : async function(promise) {await tryCatch(promise, "out of gas"         );},
    catchInvalidJump       : async function(promise) {await tryCatch(promise, "invalid JUMP"       );},
    catchInvalidOpcode     : async function(promise) {await tryCatch(promise, "invalid opcode"     );},
    catchStackOverflow     : async function(promise) {await tryCatch(promise, "stack overflow"     );},
    catchStackUnderflow    : async function(promise) {await tryCatch(promise, "stack underflow"    );},
    catchStaticStateChange : async function(promise) {await tryCatch(promise, "static state change");},
};

File MyContractTest.js:

contract("MyContractTest", function(accounts) {
    let myContract;
    let owner       = accounts[0];
    let nonOwner    = accounts[1];
    let catchRevert = require("./exceptions.js").catchRevert;

    describe("sanity assertion:", function() {
        before(async function() {
            myContract = await artifacts.require("MyContract.sol").new();
        });
        it("should complete successfully", async function() {
            await myContract.func({from: owner});
        });
        it("should abort with an error", async function() {
            await catchRevert(myContract.func({from: nonOwner}));
        });
    });
});
12
  • Can I use tryCatch() with constructor as well? For example, like this: let myContract = await tryCatch(myContract.new(), errTypes.revert);
    – Gagan
    Commented Aug 14, 2018 at 11:29
  • Because I'm trying to catch a 'revert' type error when creating an instance with wrong params to myContract's constructor. And so I'm using tryCatch as mentioned above. It works fine with other functions but I'm facing issues when used with constructor.
    – Gagan
    Commented Aug 14, 2018 at 11:32
  • 2
    @Gagan: You could probably do something like let myContract = null; await tryCatch(async function() {myContract = myContract.new()}, errTypes.revert);. And then choose your course of action according to whether or not myContract is valid. Commented Aug 14, 2018 at 12:13
  • 2
    To me, this is an incredible use of OOP.
    – Duff
    Commented Aug 30, 2018 at 21:02
  • 1
    That's exactly what truffle-assertions does nowadays, github.com/rkalis/truffle-assertions
    – Anderson
    Commented Mar 29, 2022 at 14:38
28

Most of these answers are straightforward enough, but the ones that do an inline try catch make for quite a bit of boilerplate, and the creation of the utility functions is not really something you want to do yourself.

This is exactly why I added revert and other failure testing as a feature to my truffle-assertions library. This allows you to assert reverts and other failures in a straightforward way. It also includes support for the new Truffle v5 functionality that finally enables us to retrieve the revert reason string.

The library can be installed through npm and imported at the top of the test javascript file:

npm install truffle-assertions

const truffleAssert = require('truffle-assertions');

After which it can be used inside the tests (including Truffle v5 revert reason string):

await truffleAssert.reverts(contract.revertingFunction(), "only owner");

I wrote a blog post that goes over the functionality with a detailed use case for further reading: Asserting reverts when testing Solidity smart contracts with Truffle

3
  • 1
    This is super duper cool! Commented Nov 10, 2018 at 19:16
  • 1
    Thanks, this makes the tests much cleaner. Keep up the good work :D Commented Dec 16, 2018 at 5:15
  • Saved me a lot of time. Easy and straightforward. Thank you! Commented Apr 25, 2021 at 21:40
5

You can do something like this:

it("test1.....", async() => {
  let instant = await myContract.deployed();    
  try{
    await instant.doRevert();
  }
  catch(e){
    myHandleException(e);
  }
.
.
.

myHandleException being whatever function you want.

If you're using callbacks instead of async(if you do something after your call) you can plug the catch right after the then:

  instance.otherFunction().then(function (result){
      //do things
  }).catch(function (e) {
      myHandleException(e);
  )};
.
.
.
3

Another way would be to use openzeppelin-test-helpers. It basically does the same thing as truffle-assertions, but it is a newer library.

To install you can use npm and run at the root of your project :

npm install --save-dev @openzeppelin/test-helpers

Then you can import the module into your javascript tests as follows:

const {expectRevert} = require('@openzeppelin/test-helpers');

It is used this way :

it("should fail", async () => {
    await expectRevert(contractInstance.failingFunction(), "failingStatement");
})

with failingStatement the expected exception specified in the require/revert statement.

1
  • This should be the valid answer for Openzeppelins based project.
    – kitta
    Commented Jan 19, 2022 at 13:06
0

I love the native JS answer!

Personally used truffle assertions.

It's just a few lines of code, and you can also assert specific errors occur.

npm install truffle-assertions

Then in the actual test.js file:

it("Should not be possible for anyone else to do X", async ()=>{
    let a = await ContractA.deployed();

    await truffleAssert.reverts (
        await a.onlyOwnerFunction(someArguments, {from: notOwnerAccount})
    );  
} );

This passes when the function is indeed onlyOwner, for instance by using this modifier from OpenZeppelin Ownable.

More on writing tests here: https://www.trufflesuite.com/docs/truffle/testing/writing-tests-in-javascript

0

Updated Answer: 2021

We can use the expect module from chai to catch reverts.

Solidity Contract:

Contract ContractA {
   function doRevert() public {
      revert();
   }
}

Javascript test

   import {expect} from "chai";
   it("Catch the revert", async ()=>{
      let a = await ContractA.deployed();
      await expect(a.doRevert()).to.be.revertedWith("");
   });

Solidity Contract with a revert message:

Contract ContractA {
   function doRevert() public {
      revert("Contract A reverted");
   }
}

Javascript test

   import {expect} from "chai";
   it("Catch the revert", async ()=>{
      let a = await ContractA.deployed();
      await expect(a.doRevert()).to.be.revertedWith("Contract A reverted");
   });
0

Adding to @goodvibration's answer, You can as well try to get the reason and error:

const { error: contractError, reason } = error.data[Object.keys(error.data)[0]];    
assert.strictEqual(contractError, expectedError, `Thrown error code: ${contractError} was not the expected: ${expectedError}.`);   
assert.strictEqual(reason, message, 'Thrown error message was not the expected.');

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.