You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Jest和RTL测试基于useState动态切换文本的按钮

按钮文本切换功能的Jest+RTL测试问题与解决

问题场景

我有一段JSX代码,点击按钮时状态会改变,按钮文本也会随之切换:

const [copied, setCopied] = useState(false);

<button
  className={`btn ${copied ? 'copy copied' : 'copy'}`}
  onClick={() => {
    setCopied(true);
    navigator.clipboard.writeText(shortedLink);
  }}
>
  {copied ? 'copied!' : 'copy'}
</button>

我想用Jest和React Testing Library(RTL)测试按钮的条件文本切换功能,但作为单元测试新手,尝试的两种测试方案都失败了:

报错情况

  • 第一种测试报错:value must be an HTMLElement or an SVGElement. Received has value: null
  • 第二种测试报错:Cannot read properties of undefined (reading 'type')

尝试的测试代码1

it('should change the text of the button when clicked', async () => {
    user.setup();
    render(<ShortedLink />);
    const copyBtn = screen.getByRole('button', {
      name: 'copy'
    });
    const copiedBtn = screen.queryByRole('button', {
      name: 'copied!'
    });
    expect(copyBtn).toBeInTheDocument('copy');
    expect(copiedBtn).toBe(null);
    await user.click(copyBtn);
    expect(copiedBtn).toBeInTheDocument();
});

尝试的测试代码2

it('should change the text of the button when clicked', async () => {
    user.setup();
    render(<ShortedLink />);
    const copyBtn = screen.getByRole('button', {
      name: 'copy'
    });
    expect(copyBtn).toHaveTextContent('copy');
    await user.click(copyBtn);
    expect(copyBtn).toHaveTextContent('copied!'

错误原因分析

  1. 测试代码1问题:

    • toBeInTheDocument断言不需要传参,多余的'copy'会导致错误
    • 提前定义的copiedBtn是点击前获取的null,点击后不会自动更新DOM引用,必须重新查询
    • 未模拟navigator.clipboardAPI,测试环境中不存在该API,点击时会抛出异常
  2. 测试代码2问题:

    • 存在语法错误,expect语句未闭合括号
    • 同样未模拟剪贴板API,导致点击时调用writeText失败

正确测试代码

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import ShortedLink from './ShortedLink';

// 全局模拟剪贴板API,避免测试环境报错
beforeAll(() => {
  Object.assign(navigator, {
    clipboard: {
      writeText: jest.fn().mockResolvedValue(undefined),
    },
  });
});

it('should change button text to "copied!" when clicked', async () => {
  const user = userEvent.setup();
  render(<ShortedLink />);

  // 验证初始状态按钮文本为"copy"
  const copyButton = screen.getByRole('button', { name: /copy/i });
  expect(copyButton).toHaveTextContent('copy');

  // 模拟用户点击按钮
  await user.click(copyButton);

  // 验证点击后按钮文本变为"copied!"
  expect(screen.getByRole('button', { name: /copied!/i })).toHaveTextContent('copied!');
  
  // 可选:验证剪贴板API是否被正确调用
  expect(navigator.clipboard.writeText).toHaveBeenCalled();
});

关键注意点

  • 模拟浏览器API:测试环境没有真实剪贴板,必须用Jest模拟navigator.clipboard.writeText,避免异步操作报错
  • 动态查询DOM:点击后重新用screen.getByRole查询元素,确保获取更新后的DOM节点
  • 语法严谨性:检查测试代码的括号、引号是否闭合,避免低级语法错误
  • 正确初始化用户事件:通过userEvent.setup()创建用户实例,确保异步点击操作被正确处理

内容的提问来源于stack exchange,提问作者Omar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 07:10:31