如何用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问题:
toBeInTheDocument断言不需要传参,多余的'copy'会导致错误- 提前定义的
copiedBtn是点击前获取的null,点击后不会自动更新DOM引用,必须重新查询 - 未模拟
navigator.clipboardAPI,测试环境中不存在该API,点击时会抛出异常
测试代码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
相关产品推荐
相关产品推荐

