I've written a simple React hook and want to test it with react-hooks-testing-library
.
This hook calls an async function once both provider
and domain
variables, then once it's resolved puts data in state and returns it.
I couldn't find anywhere how to test async hooks but found this: How to test custom async/await hook with react-hooks-testing-library. That question is unanswered but at least I found out that I need to call waitForNextUpdate
for testing w/ useEffect
.
The problem with my test is that it only runs the hook once (on mount) but not when provider and domain are set. So it always hangs on default value. How do I make it re-trigger the hook to actually call getENS
and set the data?
Here's how the hook looks like:
import { useEffect, useState } from 'react'
import { getENS, ResolvedENS } from 'get-ens'
import type { Provider } from '@ethersproject/providers'
const ac = new AbortController()
export const useENS = (provider: Provider, domain: string): ResolvedENS => {
const [data, set] = useState<ResolvedENS>({ address: null, owner: null, records: { web: {} } })
useEffect(() => {
const load = async () => {
const data = await getENS(provider)(domain, { signal: ac.signal })
set(data)
}
if (provider && domain) load()
return () => ac.abort()
}, [domain, provider])
return data
}
and this is how my test looks like:
import { describe, it, jest } from '@jest/globals'
import { renderHook } from '@testing-library/react-hooks'
import { useENS } from '../packages/use-ens/src/index'
import { providers } from 'ethers'
import fetch from 'fetch-mock'
jest.setTimeout(30000)
describe('use-ens', () => {
it('should return resolved data for a domain', async () => {
fetch.mock('*', {
data: {
domains: [
{
resolvedAddress: { id: '0xf75ed978170dfa5ee3d71d95979a34c91cd7042e' },
resolver: {
texts: ['avatar', 'color', 'description', 'email', 'url', 'com.github', 'com.instagram', 'com.twitter']
},
owner: { id: '0xf75ed978170dfa5ee3d71d95979a34c91cd7042e' }
}
]
}
})
const provider = new providers.InfuraProvider('homestead', 'INFURA_API_KEY')
const { result, waitForNextUpdate } = renderHook<[providers.BaseProvider, string], ReturnType<typeof useENS>>(() =>
useENS(provider, 'foda.eth')
)
console.log(result.current)
await waitForNextUpdate()
console.log(result.current)
})
})
when I run the test I get this error:
> NODE_OPTIONS=--experimental-vm-modules pnpx jest tests
console.log
{ address: null, owner: null, records: { web: {} } }
at Object.<anonymous> (tests/use-ens.test.ts:30:13)
FAIL tests/use-ens.test.ts
use-ens
✕ should return resolved data for a domain (1049 ms)
● use-ens › should return resolved data for a domain
Timed out in waitForNextUpdate after 1000ms.
30 | console.log(result.current)
31 |
> 32 | await waitForNextUpdate()
| ^
33 |
34 | console.log(result.current)
35 | })
at waitForNextUpdate (node_modules/.pnpm/@testing-library+react-hooks@7.0.1_react-dom@17.0.2+react@17.0.2/node_modules/@testing-library/react-hooks/lib/core/asyncUtils.js:102:13)
at Object.<anonymous> (tests/use-ens.test.ts:32:5)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 5.215 s
Ran all test suites matching /tests/i.
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.
console.error
Warning: An update to TestComponent inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
at TestComponent (/home/v1rtl/Coding/proj/node_modules/.pnpm/@testing-library+react-hooks@7.0.1_react-dom@17.0.2+react@17.0.2/node_modules/@testing-library/react-hooks/lib/helpers/createTestHarness.js:22:5)
13 | export const useENS = (provider: Provider, domain: string): ResolvedENS => {
14 | const [data, set] = useState<ResolvedENS>({ address: null, owner: null, records: { web: {} } })
> 15 |
| ^
16 | useEffect(() => {
17 | const load = async () => {
18 | const data = await getENS(provider)(domain, { signal: ac.signal })
console.error
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
at TestComponent (/home/v1rtl/Coding/proj/node_modules/.pnpm/@testing-library+react-hooks@7.0.1_react-dom@17.0.2+react@17.0.2/node_modules/@testing-library/react-hooks/lib/helpers/createTestHarness.js:22:5)
13 | export const useENS = (provider: Provider, domain: string): ResolvedENS => {
14 | const [data, set] = useState<ResolvedENS>({ address: null, owner: null, records: { web: {} } })
> 15 |
| ^
16 | useEffect(() => {
17 | const load = async () => {
18 | const data = await getENS(provider)(domain, { signal: ac.signal })
at printWarning (node_modules/.pnpm/react-dom@17.0.2_react@17.0.2/node_modules/react-dom/cjs/react-dom.development.js:67:30)
ERROR Test failed. See above for more details.
I have also created a gist containing Jest configuration files: https://gist.github.com/talentlessguy/aad20875b1e4406024e454485a73b1f9
Also here's the source for get-ens
library I import from: https://github.com/talentlessguy/get-ens