当前位置: 首页 > article >正文

Jest进阶知识:深入测试 React Hooks-确保自定义逻辑的可靠性

测试 React Hooks

在 React 开发中,Hooks 是一个非常重要的功能模块,允许开发者在函数组件中使用状态和其他 React 特性。自定义 Hooks 作为一种公共逻辑的抽离,经常被多个组件复用,因此对其测试是非常必要的。

然而,由于 Hooks 必须在组件内部使用,直接测试它们并不像普通函数那样简单。幸运的是,@testing-library/react-hooks 提供了一种简便的方法来测试 React Hooks。

安装和使用 @testing-library/react-hooks

首先,确保你已经安装了 @testing-library/react-hooks

npm install @testing-library/react-hooks
快速上手示例

假设我们有一个自定义 Hook useCounter,用于创建一个计数器:

// 自定义 hook
// 这是一个计数器的自定义 hook
// 内部维护了一个计数的值,以及修改这个值的一些方法

import { useState } from "react";

interface Options {
  min?: number;
  max?: number;
}

type ValueParam = number | ((c: number) => number);

// 该方法主要是做一个边界的判断,如果超过了边界,那么就取边界值
function getTargetValue(val: number, options: Options = {}) {
  const { min, max } = options;
  let target = val;
  // 判断有没有超过最大值,如果超过了,那么我们就取最大值
  if (typeof max === "number") {
    target = Math.min(max, target);
  }
  // 判断有没有超过最小值,如果超过了,那么我们就取最小值
  if (typeof min === "number") {
    target = Math.max(min, target);
  }
  return target;
}

// useCounter(100, {min : 1, max : 1000})
function useCounter(initialValue = 0, options: Options = {}) {
  const { min, max } = options;

  // 设置初始值,初始值就为 initialVaule
  // 初始值是该自定义 hook 内部维护的状态,用来表示计数器的数值
  const [current, setCurrent] = useState(() => {
    return getTargetValue(initialValue, {
      min,
      max,
    });
  });

  // 设置新的值
  // 在设置新的值的时候,调用了 getTargetValue 来判断新值是否越界
  const setValue = (value: ValueParam) => {
    setCurrent((c) => {
      const target = typeof value === "number" ? value : value(c);
      return getTargetValue(target, {
        max,
        min,
      });
    });
  };

  // 下面就是自定义 hook 提供的 4 个方法
  // 用于修改计数器的数值

  // 增加
  const inc = (delta = 1) => {
    setValue((c) => c + delta);
  };

  // 减少
  const dec = (delta = 1) => {
    setValue((c) => c - delta);
  };

  // 设置
  const set = (value: ValueParam) => {
    setValue(value);
  };

  // 重置
  const reset = () => {
    setValue(initialValue);
  };

  // 像外部暴露
  return [
    current,
    {
      inc,
      dec,
      set,
      reset,
    },
  ] as const;
}

export default useCounter;

接下来,我们将对这个自定义 Hook 进行测试。

测试同步操作
import useCounter from "../hooks/useCounter";
import { renderHook, act } from "@testing-library/react";

test("可以做加法", () => {
  // Arrange(准备)

  // result ---> {current : [0, {inc, dec, set, reset}]}
  const { result } = renderHook(() => useCounter(0));

  // Act(行为)
  act(() => result.current[1].inc(2));

  // Assert(断言)
  expect(result.current[0]).toEqual(2);
});

test("可以做减法", () => {
  // Arrange(准备)

  // result ---> {current : [0, {inc, dec, set, reset}]}
  const { result } = renderHook(() => useCounter(0));

  // Act(行为)
  act(() => result.current[1].dec(2));

  // Assert(断言)
  expect(result.current[0]).toEqual(-2);
});

test("可以设置值", () => {
  // Arrange(准备)

  // result ---> {current : [0, {inc, dec, set, reset}]}
  const { result } = renderHook(() => useCounter(0));

  // Act(行为)
  act(() => result.current[1].set(100));

  // Assert(断言)
  expect(result.current[0]).toEqual(100);
});

test("可以重置值", () => {
  // Arrange(准备)

  // result ---> {current : [0, {inc, dec, set, reset}]}
  const { result } = renderHook(() => useCounter(0));

  // Act(行为)
  act(() => result.current[1].set(100));
  act(() => result.current[1].reset());

  // Assert(断言)
  expect(result.current[0]).toEqual(0);
});

test("可以设置最大值", () => {
  // Arrange(准备)

  // result ---> {current : [0, {inc, dec, set, reset}]}
  const { result } = renderHook(() => useCounter(0, { max: 100 }));

  // Act(行为)
  act(() => result.current[1].set(1000));

  // Assert(断言)
  expect(result.current[0]).toEqual(100);
});

test("可以设置最小值", () => {
  // Arrange(准备)

  // result ---> {current : [0, {inc, dec, set, reset}]}
  const { result } = renderHook(() => useCounter(0, { min: -100 }));

  // Act(行为)
  act(() => result.current[1].set(-1000));

  // Assert(断言)
  expect(result.current[0]).toEqual(-100);
});
使用自定义 Hook

测试通过后,可以在组件中安全地使用这个自定义 Hook:

import "./App.css";
import useCounter from "./hooks/useCounter";

function App() {
  const [current, { inc, dec, set, reset }] = useCounter(5, { min: 0, max: 10 });
  return (
    <div className="App">
      <div>{current}</div>
      <button onClick={() => dec(1)}>-</button>
      <button onClick={() => inc(1)}>+</button>
      <button onClick={() => set(100)}>set</button>
      <button onClick={() => reset()}>reset</button>
    </div>
  );
}

export default App;
测试异步操作

假设我们在 useCounter 中添加了一个异步的增加方法:

const asyncInc = (delta = 1) => {
  setTimeout(() => {
    setValue((c) => c + delta);
  }, 2000);
};

测试异步操作时,可以使用 jestuseFakeTimersadvanceTimersByTime 方法来模拟时间流逝:

test("测试异步的增加", async () => {
  jest.useFakeTimers();
  const { result } = renderHook(() => useCounter(0));
  act(() => result.current[1].asyncInc(2));
  expect(result.current[0]).toEqual(0); // 初始值未变
  await act(() => jest.advanceTimersByTime(2000)); // 模拟时间流逝
  expect(result.current[0]).toEqual(2); // 值已更新
  jest.useRealTimers();
});

结论

通过本文的介绍,我们了解了如何使用 @testing-library/react-hooks 测试 React Hooks,特别是自定义 Hooks。通过对 Hooks 的行为进行测试,可以确保它们在不同情况下的表现符合预期,从而提高代码的可靠性和可维护性。


http://www.kler.cn/a/387276.html

相关文章:

  • uniapp分享功能
  • 力扣 653. 两数之和 IV 二叉树/binary-tree two-sum IV
  • 番外:MySQL的一些事务处理
  • 介绍和安装及数据类型
  • 【缓存策略】你知道 Cache Aside(缓存旁路)这个缓存策略吗
  • YOLOv11融合ICCV[2023]动态蛇形卷积Dynamic模块及相关改进思路|YOLO改进最简教程
  • yum下载时出现报错 Couldn‘t read a file:// file for file:///mnt/repodata/repomd.xml
  • 进程设计理念
  • 【sass】sass中两种去重的方法:混合 - mixin/include、继承 - extend
  • 【热门主题】000039 物联网智能项目:开启智慧未来新篇章
  • Xilinx FPGA的Vivado开发流程
  • HDR视频技术
  • C++20 概念与约束(1)—— SFINAE
  • Excel快捷键大全
  • 数据结构 C/C++(实验二:栈)
  • Node.js——fs模块-路径补充说明
  • 网络安全从零开始学习CTF——CTF基本概念
  • 使用vite构建一个react网站,并部署到Netlify上
  • DSP28335学习笔记-4
  • 计算机网络:简述LAN口模式下NAT和代理的区别
  • 【销帮帮-注册_登录安全分析报告-试用页面存在安全隐患】
  • elementUI 点击弹出时间 date-picker
  • 基于微信的追星小程序+ssm(lw+演示+源码+运行)
  • 大华Android面试题及参考答案
  • 100种算法【Python版】第50篇——Tim Sort
  • Qt:QPdfDocument渲染PDF文件时的信息丢失问题