Skip to main content

Testing

Let's take useTokenAllowance hook as an example.

Setup

First of all we'll have to setup local ethereum node and create a provider for it. We should also have access to some accounts on this network with some funds on them. All this can be easily using the Waffle testing library. You can install it using

npm install --save-dev ethereum-waffle 

And the code will look something this:

  import { MockProvider } from 'ethereum-waffle'

const mockProvider = new MockProvider()
const [deployer, spender] = mockProvider.getWallets()

Next we need to deploy all the neccesary contracts and create a useDApp config object. useDApp requires Multicall contract to be deployed in order to be able to query blockchain. @usedapp/core package is shipped together with the Multicall object, that contains all the neccesary data for deployment.

  import { MultiCall } from '@usedapp/core'

const multicall = await deployContract(deployer, Multicall)

Now we have to create a useDApp config object. Let's populate it with the following fields:

  • readOnlyChainId: the default chain that useDApp queries.
  • readOnlyUrls: this object contains info on how to access specific chains. In this case we're using the MockProvider object, so we'll pass it to the config object.
  • multicallAddresses: addresses of Multicall contracts on specific chains. Here we'll use the address of the contract that we deployed before.
  const chainId = (await mockProvider.getNetwork()).chainId
const config = {
readOnlyChainId: chainId,
readOnlyUrls: {
[chainId]: mockProvider
},
multicallAddresses: {
[chainId]: multicall.address
}
}
note

You could also use the Multicall2 contract. It is also exported from the @usedapp/core package. In this case you would also need to specify multicallVersion field in your config object (set it to 2).

The last step would be to deploy the mock ERC20 token that we'll use in the tests. The @usedapp/core package exports the token contract that we'll use.

  import { MockERC20 } from '@usedapp/core'

let token: Contract

const tokenArgs = [
'MOCKToken', // name of the token
'MOCK', // symbol of the token
deployer.address, // address of the token owner
utils.parseEther('10') // total supply of the token
]

token = await deployContract(deployer, MockERC20)

Now we're ready to do the actual testing.

Testing

To check if the hook reads data correctly, we need to prepare it first. We approve the spender so that we can check if our hook returns the correct value.

To test the hook we need to render it using renderDAppHook. It works like renderHook from the react-testing-library, but it wraps the hook with additional contexts used by useDApp. You can also pass the config object to the renderDAppHook method - that works in the same way as passing config to the DAppProvider component in the regular dApp.

React components are asynchronous. Reading data from the blockchain is also an async operation. To get the return value from the hook, wait for the result to be set. You can do it with waitForCurrent.

Then we can check if our result is correct. result.current is a value returned from our hook. It should be equal to 1 Ether.

  await token.approve(spender.address, utils.parseEther('1'))

const { result, waitForCurrent } = await renderDAppHook(
() => useTokenAllowance(token.address, deployer.address, spender.address),
{
config,
}
)
await waitForCurrent((val) => val !== undefined)

expect(result.error).to.be.undefined
expect(result.current).to.eq(utils.parseEther('1'))

Full example

  import type { Contract } from 'ethers'
import { useTokenAllowance, Config, MultiCall, ERC20Mock } from '@usedapp/core'
import { renderDAppHook } from '@usedapp/testing'
import chai, { expect } from 'chai'
import { MockProvider, deployContract, solidity } from 'ethereum-waffle'
import { utils } from 'ethers'

chai.use(solidity)

describe('useTokenAllowance', () => {
const mockProvider = new MockProvider()
const [deployer, spender] = mockProvider.getWallets()

let token: Contract
let config: Config

beforeEach(async () => {
const multicall = await deployContract(deployer, MultiCall)
const chainId = (await mockProvider.getNetwork()).chainId
config = {
readOnlyChainId: chainId,
readOnlyUrls: {
[chainId]: mockProvider
},
multicallAddresses: {
[chainId]: multicall.address
}
}
const tokenArgs = [
'MOCKToken',
'MOCK',
deployer.address,
utils.parseEther('10')
]
token = await deployContract(deployer, ERC20Mock, tokenArgs)
})

it('returns current allowance', async () => {
await token.approve(spender.address, utils.parseEther('1'))

const { result, waitForCurrent } = await renderDAppHook(
() => useTokenAllowance(token.address, deployer.address, spender.address),
{
config,
}
)
await waitForCurrent((val) => val !== undefined)

expect(result.error).to.be.undefined
expect(result.current).to.eq(utils.parseEther('1'))
})
})