Published on

Truffle Tests are the Fastest Way to Debug DApp Issues

Authors

Last week while wrapping up a dApp we ran into various issues we had to debug and dig into to try the fix. My initial approach to dealing with these bugs was:

  • Move dependent contracts into the same project.
    • To make it easier and faster to debug issues by trying different things without having to truffle migrate into 2 or more projects.
  • Add test routes to the UI that automate some of the manual actions.
    • For example, we needed to call a bunch of dependant contracts before the chain was in a state where we could start debugging.
  • Test the change that may address the issue.
  • Switch to another account that has to be in a certain state and test change B that is related to what we did in an earlier step.

Despite my best efforts to streamline and speed up this process, the feedback loop for debugging was incredibly long and cumbersome.

Using Truffle Tests to Streamline the Process

At some point in this process, I realized that using truffle to debug and resolve this issue was significantly faster and the quickest way to actually debug and resolve issues. When I speak of tests here I am not referring to them from a TDD context but rather as a mechanism to quickly run the same steps we run in our dApp but in a very controlled and fast way.

Pretend we have the following contracts:

pragma solidity ^0.4.18;
contract ContractA {

  function someAction() public {
    // some state change happens here
  }
}

And:

pragma solidity ^0.4.18;
contract ContractB {

  function someActionB() public {
    // some other action that varies depending on whether contractA.someAction() has happened or not and the user that calls it
  }
}

We then create a test file lets say contractA-contractB.test.js. Where we run the same steps we will in our dApp for example:

const ContractA = artifacts.require('ContractA')
const ContractB = artifacts.require('ContractB')

contract('ContractA', ([owner, account1, account2, account3, account4]) => {
  let contractA
  let contractB

  beforeEach('Common setup for all tests', async () => {
    contractA = await ContractA.new()
    contractB = await ContractB.new()
  })

  describe('The steps we will do in our dApp', async () => {
    it('Should do what we expect it to in our dApp when someAction happens first', async () => {
      await contractA.someAction()

      await contractB.someActionB.send({ from: account1 })

      // assert or/and console log out here what you need to check
    })
  })
})

Testing changes to our contract/s are now as simple as:

  • Make the changes in the contracts
  • Run truffle test

Using the above approach is a significantly faster way to debug and test the actions we have in our dApp. We now simply get it working in our test, once it works there we apply the changes to the dApp and test once more that it still works on the front end.