0

测试React Query

 1 year ago
source link: https://www.ttalk.im/2022/06/testing-react-query.html?amp%3Butm_medium=Atom
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

测试React Query

由 David Gao 发布于: 2022-06-27

翻译, JavaScript, 前端

介绍如何对包含React Query的组件进行测试

testing.jpg

原文地址: Testing React Query

在React Query常见的问题中,我们经常可以看到一些关于测试这一主题的问题,所以我将在这里尝试回答其中的一些问题。我认为其中一个主要原因是测试“聪明”组件(通常被叫做容器组件)并非一件容易的事情。随着Hooks的广泛应用,这种拆分技巧已经被大量的废弃掉了。现在鼓励在需要的地方直接使用hooks,而不是进行大量任意拆分和向下传递props。

我认为这是对代码共享和代码可读性的一个很好的普适性的提升,但是我们现在有更多的组件需要“仅仅props属性”之外的依赖项。

它们可能会用 useContext。也可能会去使用 useSelector。它们还有可能使用 useQuery

这些组件在技术上讲不再是无副作用的了,因为在不同的环境中调用它们会导致不同的结果。 在测试它们时,我们需要仔细设置其所需要的环境以使其正常工作。

模拟网络请求

由于React Query是一个异步服务器状态管理库,我们的组件可能会向后端发出请求。 在测试时,此后端无法实际交付数据,即使后端是可以给出正确的数据,我们可能也不想让我们的测试依赖于它。

有大量关于如何用使用jest模拟数据的文章。 如果你已经在使用了,你完全可以模拟你的api客户端。你可以直接模拟fetch或axios。但是我只支持Kent C. Dodds 在他的文章停止模拟fetch吧中所写的内容:

请使用@ApiMocking开发的mock service worker

在模拟我们的api时,它可能是我们真正的单一数据源:

  1. 可以在node中进行测试
  2. 支持REST和GraphQL
  3. 具备stroybook插件,可以在stories中使用useQuery
  4. 在进行开发的过程中,我们可以在浏览器devtools中看到发出的请求
  5. 可以和fixtures类似的cypress协同工作

处理好我们的网络层后,我们可以开始讨论React Query需要关注的具体事项:

QueryClientProvider

无论何时我们需要使用React Query,我们都需要使用QueryClientProvider并为它提供一个queryClient,queryClient是一个 QueryCache 的容器。这个缓存将保存我们所有的查询数据。

我更喜欢为每个测试提供一个独占的QueryClientProvider并为每个测试创建一个新的QueryClient。这样,测试就完全相互隔离了。另一种方法可能是在每次测试后清除缓存,但我希望尽可能减少测试之间的共享状态。否则,如果我们并行运行测试,我们可能会得到意外和不稳定的结果。

自定义的hooks

如果我们正在测试自定义hooks,我很确定绝大部分人正在使用 react-hooks-testing-library。测试hooks是最简单的事情。使用该库,我们可以将我们的hooks包装在一个包装器中,这是一个 React组件,用于在渲染时包装测试组件。我认为这是创建QueryClient的理想场所,因为每次测试都会执行一次:

const createWrapper = () => {
  // ✅ 为每次测试创建一个全新的QueryClient
  const queryClient = new QueryClient()
  return ({ children }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>)
}

test("my first test", async () => {
  const { result } = renderHook(() => useCustomHook(), {
    wrapper: createWrapper()
  })
}

如果要测试使用useQuery的组件,还需要将该组件包装在 QueryClientProvider中。来自react-hooks-testing-library简单渲染包装器似乎是一个不错的选择。让我们看看React Query如何进行内部进行测试

关掉请求重试

这是React Query测试中最常见的“陷阱”之一:React Query默认使用指数退避的三次重试,这意味着如果我们想测试查询出错的场景,我们的测试可能会超时。关闭重试的最简单方法是通过QueryClientProvider。 让我们扩展上面的例子:

const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        // ✅ 关闭重试
        retry: false,
      },
    },
  })

  return ({ children }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  )
}

test("my first test", async () => {
  const { result } = renderHook(() => useCustomHook(), {
    wrapper: createWrapper()
  })
}

这会将组件树中所有查询的默认值设置为“不重试”。重要的是要知道,这仅在我们在使用useQuery时没有明确的重试设置时才有效。如果我们有一个需要重试5次的查询,显示传入的参数会被优先使用,因为默认值仅作为后备选项。

setQueryDefaults

对于这个问题,我能给大家的最好建议是:不要直接在useQuery上设置这些选项。 尝试尽可能使用和覆盖默认值,如果您确实需要为特定查询更改某些内容,请使用 queryClient.setQueryDefaults

因此让我举个例子,为了替代在 useQuery 上设置重试的方案:

const queryClient = new QueryClient()
function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  )
}

function Example() {
  // 🚨 我们将无法在测试中覆盖该选项
  const queryInfo = useQuery('todos', fetchTodos, { retry: 5 })
}

我们应该使用下面的方案:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 2,
    },
  },
})

// ✅ 只有todos才进行5次重试

queryClient.setQueryDefaults('todos', { retry: 5 })

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  )
}

在这里,所有查询都会重试两次,只有 todos 会重试五次,我们仍然可以选择在我们的测试中为所有查询关闭它🙌。

ReactQueryConfigProvider

当然,这只适用于已知的查询键。 有时,我们真的想在组件树的子集上设置一些配置。 在React Query的v2版本中,有一个针对该确切用例的 ReactQueryConfigProvider(该功能在v2以后的版本中被统一到了QueryClientProvider 上)。 我们可以通过几行代码在React Query的v3版本中实现相同的目标:

const ReactQueryConfigProvider = ({ children, defaultOptions }) => {
  const client = useQueryClient()
  const [newClient] = React.useState(
    () =>
      new QueryClient({
        queryCache: client.getQueryCache(),
        muationCache: client.getMutationCache(),
        defaultOptions,
      })
  )
  return (
    <QueryClientProvider client={newClient}>{children}</QueryClientProvider>
  )
}

我们可以在codesandbox的例子中看它如何工作。

总是等待Query

由于React Query本质上是异步的,因此在运行hook时,我们不会立即得到结果。 它通常处于加载状态,没有要检查的数据。react-hooks-testing-library中的异步工具集提供了很多解决此问题的方法。 对于最简单的情况,我们可以等到查询转换为成功状态:

const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
      },
    },
  })
  return ({ children }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  )
}


test("my first test", async () => {
  const { result, waitFor } = renderHook(() => useCustomHook(), {
    wrapper: createWrapper()
  })
  // ✅ 等待,直到查询状态变为成功
  await waitFor(() => result.current.isSuccess)
  expect(result.current.data).toBeDefined()
}

@testing-library/react v13.1.0中我们依然可以使用renderHook。但是它并没有返回自己的 waitFor,因此我们需要从@testing-library/react中引入。它和原有的API设计稍有不同,它并不会准许返回布尔值,它期望的返回值是一个 Promise。因此我们需要对我们的代码稍作修改:

import { waitFor, renderHook } from '@testing-library/react'
test("my first test", async () => {
  const { result } = renderHook(() => useCustomHook(), {
    wrapper: createWrapper()
  })
  // ✅为waitFor返回一个Promise
  await waitFor(() => expect(result.current.isSuccess).toBe(true))

  expect(result.current.data).toBeDefined()
}

静音控制台或终端错误输出

默认情况下,React Query会将错误打印到控制台。我认为这在测试期间产生了太多的干扰,因为即使所有测试都是🟢,但是我们也会在控制台中看到🔴。 React Query允许通过设置日志选项来覆盖默认行为,所以这就是我们通常需要做的事情:

import { setLogger } from 'react-query'
setLogger({
  log: console.log,
  warn: console.warn,
  // ✅不要在控制台或者终端中输出错误
  error: () => {},
})

更新: setLogger 在v4中被移除了。相反,我们可以将自定义日志设置作为props传递给我们创建的 QueryClient

const queryClient = new QueryClient({
  logger: {
    log: console.log,
    warn: console.warn,
    // ✅ 不要在控制台或者终端中输出错误
    error: () => {},
  }
})

此外,不再在生产模式下记录错误日志可以避免对我们造成困扰。

把它们放在一起

我已经建立了一个github的项目仓库,所有这些都很好地结合在一起:mock-service-worker、react-testing-library和提到的包装器。同时它包含四个测试用例——测试非常基本的自定义hook和组件产生失败和成功的结果。可以访问此处:testing-react-query

好的单元测试,可以充分避免我们在进行业务重构时,因为不“谨慎”而产生的新错误,从而有效的保证我们的业务的稳定性。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK