使用 React Testing Library 测试自定义 React Hooks


自定义 React hooks为开发人员提供了在多个组件之间提取和重用常见功能的能力。然而,测试这些 hooks可能会有些棘手,特别是对于测试新手来说。在本文中,我们将探讨如何使用 React Testing Library 测试自定义 React hook。

测试 React组件

首先,让我们回顾一下如何测试一个基本的React组件。我们来考虑一个名为Counter的组件的例子,该组件显示一个计数和一个在点击时增加计数的按钮。Counter 组件接受一个可选的prop,名为initialCount,如果未提供,则默认为零。以下是代码:

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. 使用 React Testing Libraryrender 函数渲染组件。
    1. 使用 React Testing Libraryscreen对象获取 DOM元素。ByRole 是推荐的查询元素的方法。
    1. 使用 @testing-library/user-event库模拟用户事件。
    1. 对渲染输出进行断言。

以下测试验证了Counter 组件的功能:

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。在第二个测试中,我们为 initialCount prop 传入了值1,并测试渲染的计数值是否也为1

最后,第三个测试检查 Counter 组件在点击增加按钮后是否正确更新了计数。

测试自定义 React hooks

现在,让我们看一个自定义hook的例子以及如何使用React Testing Library进行测试。我们已将计数逻辑提取到名为 useCounter 的自定义React hook中。

hook 接受一个初始计数作为可选prop,并返回一个具有当前计数值和增加函数的对象。以下是useCounter hook 的代码:

// 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 };
};

使用这个自定义hook,我们可以很容易地向我们React应用的任何组件添加计数功能。现在,让我们探讨如何使用 React Testing Library进行测试。

// 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() hook,并使用 result 对象获取其返回值。然后,我们使用 expect()验证初始计数是否为 0。

请注意,值保存在result.current 中。将 result视为最近提交值的引用。

使用 renderHook() 的option

我们还可以通过将选项对象作为 renderHook()函数的第二个参数传递来测试 hook是否接受并呈现相同的初始计数:

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属性设置为10options对象传递给我们的useCounter()钩子。然后使用expect()验证计数是否等于10

使用 act() 更新状态

对于我们的最后一个测试,让我们确保增加功能按预期工作。

为了测试 useCounter() hookincrement功能是否按预期工作,我们可以使用 renderHook()渲染 hook并调用 result.current.increment()

然而,当我们运行测试时,它失败并显示错误消息:“Expected count to be 1 but received 0”

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

错误消息还提供了出错的线索:“An update to TestComponent inside a test was not wrapped in act(...).”。这意味着导致状态更新的代码,在这种情况下是增加函数,应该被包装在 act(...)中。


React Testing Library中, act() 辅助函数在确保组件的所有更新被完全处理后再进行断言方面发挥着至关重要的作用。

具体来说,当测试涉及状态更新的代码时,将该代码与 act()函数一起包装是非常重要的。这有助于准确模拟组件的行为,并确保测试反映真实世界的场景。

请注意,act()React Testing Library提供的一个辅助函数,用于包装导致状态更新的代码。尽管该库通常会将所有此类代码包装在 act()中,但在测试直接调用导致状态更新的函数的自定义hook时,这是不可能的。在这种情况下,我们需要手动使用 act()将相关代码包装起来。

// 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());
    expect(result.current.count).toBe(1);
});

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

结论

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

此外,我们必须确保任何导致状态更新的代码都被act()实用工具函数包装起来,以防止错误发生。



喜欢的朋友记得点赞、收藏、关注哦!!!

React自定义Hooks是一种自定义React Hook函数,用于在无需编写类组件的情况下,复用和共享状态逻辑。通过使用自定义Hooks,我们可以将组件中的逻辑提取出来,以便在多个组件之间共享和重用。引用中提到的HookReact 16.8新增的特性,它可以让我们在函数组件中使用状态和其他React特性,而无需编写类组件。而引用中的useUpdateEffect是一个自定义React Hook,它类似于React的useEffect,但是只在组件更新时执行副作用操作,忽略了组件的初始渲染阶段。 使用React自定义Hooks的步骤如下: 1. 创建自定义Hook函数,函数名以"use"开头,例如"useCustomHook"。 2. 在自定义Hook函数中,可以使用其他React Hooks和自定义逻辑来实现所需的功能。 3. 在组件中通过调用自定义Hook函数来使用Hook,以获取状态和执行副作用操作。 下面是一个示例,展示如何创建和使用一个自定义Hook来管理计数器的状态: ```javascript // 自定义Hook import { useState } from 'react'; function useCounter(initialValue) { const [count, setCount = useState(initialValue); const increment = () => { setCount(count + 1); }; const decrement = () => { setCount(count - 1); }; return { count, increment, decrement }; } // 使用自定义Hook的组件 import React from 'react'; import useCounter from './useCounter'; function Counter() { // 使用useCounter自定义Hook,获取计数器的状态和操作函数 const { count, increment, decrement } = useCounter(0); return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> ); } ``` 在上述示例中,我们创建了一个名为useCounter的自定义Hook函数,它使用useState来管理计数器的状态,并提供了increment和decrement函数来增加和减少计数器的值。然后,我们在Counter组件中使用useCounter来获取计数器的状态和操作函数,并展示计数器的值以及按钮来增加和减少计数器的值。 通过使用React自定义Hooks,我们可以轻松地复用和共享状态逻辑,使代码更加可维护和可扩展。通过创建自定义Hooks,我们可以将组件的逻辑分离出来,提高代码的重用性和可读性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值