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

如何遵循React Testing Library原则测试React组件

React Feed 组件 RTL 最佳实践测试方案

核心原则全程对齐React Testing Library要求:只验证用户可感知的交互和渲染结果,不触碰组件内部状态、不硬编码实现逻辑。


初始测试存在的问题

你当前写的基础用例有几个明显缺陷,会导致测试无效或结果不稳定:

  • 没有传入组件必填的feedChoice、setFeedChoice两个props,运行时会直接抛错
  • 没有控制自定义钩子useWindowDimension的返回值,测试环境默认视口宽度不固定,用例结果随机
  • 没有覆盖移动端折叠展开、桌面端布局差异、选项点击回调这些核心功能场景

前置准备

  1. 视口钩子mock说明
    我们mockuseWindowDimension不属于测试实现细节:这个钩子是对浏览器窗口resize API的封装,控制它的返回值本质是在模拟用户调整窗口、切换设备的真实场景,和实际使用场景完全一致。
  2. 依赖说明
    使用@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 12:57:18