1

我写了一个简单的 React 钩子,想用react-hooks-testing-library.

这个钩子调用一个异步函数一次providerdomain变量,然后一旦它被解决,就会将数据置于状态并返回它。

我在任何地方都找不到如何测试异步钩子,但发现了这个:How to test custom async/await hook with react-hooks-testing-library。这个问题没有答案,但至少我发现我需要调用waitForNextUpdatew/ 进行测试useEffect

我的测试的问题是它只运行一次钩子(在挂载时),而不是在设置提供者和域时。所以它总是挂在默认值上。如何让它重新触发钩子以实际调用getENS和设置数据?

这是钩子的样子:

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
}

这就是我的测试的样子:

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)
  })
})

当我运行测试时,我收到此错误:

> 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.

我还创建了一个包含 Jest 配置文件的要点:https ://gist.github.com/talentlessguy/aad20875b1e4406024e454485a73b1f9

这里也是get-ens我导入的库的来源:https ://github.com/talentlessguy/get-ens

4

1 回答 1

1

async/await 没有正确转译来执行 useEffect 清理。我不知道为什么。我自己也遇到了同样的问题。

如果使用 Promise 样式编写,useEffect 清理将正常运行。此外,您可能需要为每个 useEffect 使用一个新的 Abort Controller。

试试这样:

useEffect(() => {
  const ac = new AbortController()
  if (provider && domain) {
    getENS(provider)(domain, { signal: ac.signal }).then((data)=>{
      set(data)
    }
  }
  return () => ac.abort()
}, [domain, provider])
于 2021-10-15T16:51:13.547 回答