如何遵循React Testing Library原则测试React组件
React Feed 组件 RTL 最佳实践测试方案
核心原则全程对齐React Testing Library要求:只验证用户可感知的交互和渲染结果,不触碰组件内部状态、不硬编码实现逻辑。
初始测试存在的问题
你当前写的基础用例有几个明显缺陷,会导致测试无效或结果不稳定:
- 没有传入组件必填的
feedChoice、setFeedChoice两个props,运行时会直接抛错 - 没有控制自定义钩子
useWindowDimension的返回值,测试环境默认视口宽度不固定,用例结果随机 - 没有覆盖移动端折叠展开、桌面端布局差异、选项点击回调这些核心功能场景
前置准备
- 视口钩子mock说明
我们mockuseWindowDimension不属于测试实现细节:这个钩子是对浏览器窗口resize API的封装,控制它的返回值本质是在模拟用户调整窗口、切换设备的真实场景,和实际使用场景完全一致。 - 依赖说明
使用@testing-library/user-event模拟用户点击,比内置的fireEvent更贴近真实浏览器的交互事件序列。
分场景测试用例设计
所有用例完全从用户视角出发,不读取/断言组件内部的isDropdown状态,所有判断都基于屏幕可见元素:
桌面端场景(视口宽度 > 600px)
验证点:
- 4个Feed选项始终全部可见
- 选项间存在3个视觉分割线
- 点击任意选项,会触发外部传入的
setFeedChoice回调,返回对应选项类型
移动端场景(视口宽度 ≤ 600px)
验证点:
- 默认收起状态下,仅展示当前选中的单个选项,其余选项不在DOM中
- 点击当前展示的选中项,会展开下拉面板,4个选项全部可见
- 点击面板中任意选项,面板自动收起,仅展示新选中的选项,同时触发
setFeedChoice回调返回对应类型
完整测试代码示例
import React from 'react' import { screen, render } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { beforeEach, describe, expect, it, vi } from 'vitest' // 使用Jest则替换为Jest对应API import Feed from '../components/feed/feed' import { QueryType } from '../constants' // mock视口钩子,测试中可动态修改返回的width值 const mockUseWindowDimension = vi.fn() vi.mock('../hooks/useWindowDimensions', () => ({ default: () => mockUseWindowDimension() })) describe('Feed组件', () => { const mockSetFeedChoice = vi.fn() const user = userEvent.setup() beforeEach(() => { vi.clearAllMocks() }) it('桌面端宽度>600px时,正常展示全量选项和分割线', async () => { // 模拟桌面端视口 mockUseWindowDimension.mockReturnValue({ width: 1200 }) render(<Feed feedChoice={QueryType.New} setFeedChoice={mockSetFeedChoice} />) // 验证所有选项可见 expect(screen.getByText(/new/i)).toBeInTheDocument() expect(screen.getByText(/boost/i)).toBeInTheDocument() expect(screen.getByText(/comments/i)).toBeInTheDocument() expect(screen.getByText(/squash/i)).toBeInTheDocument() // 验证3个分割线存在 expect(document.querySelectorAll('.divider')).toHaveLength(3) // 验证点击选项触发回调 await user.click(screen.getByText(/boost/i)) expect(mockSetFeedChoice).toHaveBeenCalledWith(QueryType.Boost) }) it('移动端宽度≤600px时,默认仅展示当前选中项', () => { // 模拟移动端视口 mockUseWindowDimension.mockReturnValue({ width: 375 }) render(<Feed feedChoice={QueryType.New} setFeedChoice={mockSetFeedChoice} />) // 仅选中项可见 expect(screen.getByText(/new/i)).toBeInTheDocument() expect(screen.queryByText(/boost/i)).not.toBeInTheDocument() expect(screen.queryByText(/comments/i)).not.toBeInTheDocument() expect(screen.queryByText(/squash/i)).not.toBeInTheDocument() // 移动端无分割线 expect(document.querySelectorAll('.divider')).toHaveLength(0) }) it('移动端点击选中项可展开下拉面板,展示全量选项', async () => { mockUseWindowDimension.mockReturnValue({ width: 375 }) render(<Feed feedChoice={QueryType.New} setFeedChoice={mockSetFeedChoice} />) // 点击当前展示的选项 await user.click(screen.getByText(/new/i)) // 展开后所有选项可见 expect(screen.getByText(/boost/i)).toBeInTheDocument() expect(screen.getByText(/comments/i)).toBeInTheDocument() expect(screen.getByText(/squash/i)).toBeInTheDocument() }) it('移动端展开后点击任意选项,自动收起面板并触发选择回调', async () => { mockUseWindowDimension.mockReturnValue({ width: 375 }) render(<Feed feedChoice={QueryType.New} setFeedChoice={mockSetFeedChoice} />) // 先展开面板 await user.click(screen.getByText(/new/i)) // 点击Comments选项 await user.click(screen.getByText(/comments/i)) // 验证回调触发 expect(mockSetFeedChoice).toHaveBeenCalledWith(QueryType.Comments) // 验证面板收起,其他选项不可见 expect(screen.queryByText(/boost/i)).not.toBeInTheDocument() expect(screen.queryByText(/squash/i)).not.toBeInTheDocument() }) })
注:如果需要测试视口动态变化的场景(比如用户拖动窗口从桌面端大小调整到移动端大小),只需要在单个用例中修改
mockUseWindowDimension的返回值,然后触发组件重渲染即可,同样不需要修改组件内部状态,完全模拟真实用户调整窗口的操作。
内容的提问来源于stack exchange,提问作者AAMCODE
相关产品推荐
相关产品推荐

