前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >搞懂了,React 中原来要这样测试自定义 Hooks

搞懂了,React 中原来要这样测试自定义 Hooks

作者头像
前端修罗场
发布2023-10-07 19:57:30
4150
发布2023-10-07 19:57:30
举报
文章被收录于专栏:Web 技术

React 中自定义的 Hooks 为开发者提供了重用公共方法的能力。然而,如果你是一个测试新手的话,测试这些钩子可能会很棘手。本文中,我们将探索如何使用 React Testing Library 测试库来测试自定义钩子。

如何测试 React 组件

开始前,首先让我们回顾一下如何测试一个基本的 React 组件。我这里提供一个 Counter 组件的例子,该组件显示一个计数和一个按钮,当单击该按钮时,计数会增加。其中,Counter 组件接受一个名为 initialCountprops,如果没有提供,该 props 默认为 0。例子的代码如下所示:

代码语言:javascript
复制
import { useState } from 'react'

type UseCounterProps = {
  initialCount?: number
}

export const Counter = ({ initialCount = 0 }: CounterProps = {}) => {
  const [count, setCount] = useState(initialCount)
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

如果使用 React Testing Library 测试 Counter 组件,通常需要遵循以下步骤:

  1. 使用 Render 函数来渲染组件。
  2. 使用 screen 对象获取 DOM 元素(可以使用 ByRole 来查询元素)。
  3. 使用 @testing-library/user-event 库模拟用户事件。
  4. 对呈现的输出进行断言。

以下测试中,我们依据上述的步骤来验证 Counter 组件的功能:

代码语言:javascript
复制
import { render, screen } from '@testing-library/react'
import { Counter } from './Counter'
import user from '@testing-library/user-event'

describe('Counter', () => {
  test('renders a count of 0', () => {
    render(<Counter />)
    const countElement = screen.getByRole('heading')
    expect(countElement).toHaveTextContent('0')
  })

  test('renders a count of 1', () => {
    render(<Counter initialCount={1} />)
    const countElement = screen.getByRole('heading')
    expect(countElement).toHaveTextContent('1')
  })

  test('renders a count of 1 after clicking the increment button', async () => {
    user.setup()
    render(<Counter />)
    const incrementButton = screen.getByRole('button', { name: 'Increment' })
    await user.click(incrementButton)
    const countElement = screen.getByRole('heading')
    expect(countElement).toHaveTextContent('1')
  })
})
  • 第一个测试:验证 Counter 组件是否在默认情况下以 0 计数呈现。
  • 第二个测试:我们传入 props: initialCount 的值为1,并测试呈现的计数值是否也是1。
  • 第三个测试:检查在单击 Increment 按钮后 Counter 组件是否正确更新计数。

好了,上面我们测试了 React 基础组件。接下来,再来测试自定义 Hooks。

测试自定义 Hooks

首先,我们先编写一个自定义 Hooks,接着我们再使用 React Testing Library 对它进行测试。

下面这段代码,你看到的是我将前面计算器的逻辑提取到一个名为 useCounter 的自定义钩子中:

代码语言:javascript
复制
// useCounter.tsx
import { useState } from "react";

type UseCounterProps = {
  initialCount?: number
}

export const useCounter = ({ initialCount = 0 }: CounterProps = {}) => {
  const [count, setCount] = useState(initialCount);

  const increment = () => {
    setCount((prevCount) => prevCount + 1);
  };

  return { count, increment };
};

接着,让我们来探索一下如何使用 React Testing Library 对它进行测试:

代码语言:javascript
复制
// useCounter.test.tsx
import { render } from "@testing-library/react";
import { useCounter } from "./useCounter";

describe("useCounter", () => {
  test("should render the initial count", () => {
    render(useCounter) // error
  });
})

这时候你会发现上面这段代码在执行的时候会有一些问题,在下面的内容中我会进行阐述。

测试自定义 Hooks 时遇到的问题

测试自定义钩子不同于测试组件。当你尝试将钩子传递给 render() 函数来测试钩子时,你将收到一个类型错误指示该钩子不能分配给 ReactElement<any, string | JSXElementConstructor<any>> 类型的参数。这是因为自定义钩子不返回任何JSX,这与 React 组件是不同的

另一方面,如果你试图在不使用 render() 函数的情况下调用自定义 hooks,也会在终端中看到错误,终端会指出 hooks 只能在函数组件中调用

这么看来,测试自定义钩子确实有些棘手。

不过,别灰心,我的解决办法马上就要来了!

使用 renderHook() 测试自定义 Hooks

要在 React 中测试自定义钩子,我们可以使用 React Testing Library 测试库提供的 renderHook() 函数。这个函数允许我们渲染一个钩子并访问它的返回值

接下来,在下面的代码中,让我们看看如何使用 renderHook() 重写 useCounter() 钩子的测试用例:

代码语言:javascript
复制
// useCounter.test.tsx
import { renderHook } from "@testing-library/react";
import { useCounter } from "./useCounter";

describe("useCounter", () => {
  test("should render the initial count", () => {
    const { result } = renderHook(useCounter);
    expect(result.current.count).toBe(0);
  });
})

在这个测试中,我们使用 renderHook() 来渲染 useCounter() 钩子,并使用 result 对象获得它的返回值。然后使用 expect() 去验证初始计数是否为 0。

需要注意的是,该值保存在 result.current 中。

renderHook() 的 options 对象

同时,我们也可以通过传递一个 options 对象作为 renderHook() 的第二个参数来测试钩子是否接受并渲染相同的初始计数:

代码语言:javascript
复制
test("should accept and render the same initial count", () => {
    const { result } = renderHook(useCounter, {
      initialProps: { initialCount: 10 },
    });
    expect(result.current.count).toBe(10);
});

在这个测试中,我们使用 renderHook() 函数的 initialProps 选项将一个 initialCount 属性设置为 10 的 options 对象传递给我们的 useCounter() 钩子。然后使用 expect() 验证计数是否等于 10

接下来,让我们来看看如何测试事件。

使用 act() 来更新 state

为了测试 useCounter() 钩子的 increment 按钮功能是否如预期的那样工作,我们可以使用 renderHook() 来渲染钩子并调用 result.current.increment() 方法。

然而,当我们运行测试时,失败了,并显示一条错误信息:

代码语言:javascript
复制
Expected: 1
Received: 0
代码语言:javascript
复制
test("should increment the count", () => {
    const { result } = renderHook(useCounter);
    result.current.increment();
    expect(result.current.count).toBe(1);
});

不过,错误信息提供了一个线索,指明了哪里出了问题:“在测试中对 TestComponent 的更新没有封装在 act(…) 中。

这么说,我们应该把 increment 方法,包装在 act(…) 中。

在 React Testing Library 中,act() 辅助函数会确保对组件进行的所有更新是在做出断言之前都能得到充分的处理。特别是在测试涉及状态更新的代码时,必须用 act() 函数包装该代码。这有助于准确地模拟组件的行为,并确保测试反映出真实的场景。

因此,我们对测试代码进行如下更改:

代码语言:javascript
复制
// useCounter.test.tsx
import { renderHook, act } from '@testing-library/react'
import { useCounter } from './useCounter'

test("should increment the count", () => {
    const { result } = renderHook(useCounter);
    act(() => result.current.increment()); // + update code
    expect(result.current.count).toBe(1);
});

通过用 act() 包装 increment() 函数,我们可以确保在执行断言之前应用对状态的任何修改。这种方法还有助于避免由于异步更新而产生的潜在错误。

至此,我们完成了对自定义 Hooks 的测试工作。

总结

当使用 React Testing Library 测试自定义钩子时,我们使用 renderHook() 函数来渲染我们的自定义钩子,并验证它是否返回预期的值。如果我们的自定义钩子接受props,我们可以使用 renderHook() 函数的 initialProps 选项传递它们。

此外,我们必须确保任何导致状态更新的代码都用 act() 辅助函数包装,以防止出现错误。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 如何测试 React 组件
  • 测试自定义 Hooks
    • 测试自定义 Hooks 时遇到的问题
      • 使用 renderHook() 测试自定义 Hooks
        • renderHook() 的 options 对象
          • 使用 act() 来更新 state
          • 总结
          相关产品与服务
          腾讯云服务器利旧
          云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档