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
- Yarn
npm install --save-dev ethereum-waffle
yarn add --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 thatuseDApp
queries.readOnlyUrls
: this object contains info on how to access specific chains. In this case we're using theMockProvider
object, so we'll pass it to the config object.multicallAddresses
: addresses ofMulticall
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'))
})
})