返回首页

React Testing Library:放弃快照

本文描述了项目中从 Enzyme 迁移到 React Testing Library 的过程。快照和实现测试问题的分析。编写和审查测试的检查清单、meetup 和迁移推荐。

使用 RTL 而非 Enzyme:React 行为测试
Advertisement 728x90

从快照到行为:迁移到 React Testing Library

在典型项目中,测试往往聚焦于标记结构而非实际功能。快照测试捕获的是 DOM 状态——但一旦重构,比如更改 CSS 类名、调整属性顺序或优化渲染逻辑,测试就会失效。真正的影响逻辑错误却依然无法被发现。

使用 Enzyme 的问题测试示例

describe("UserProfileCards", () => {
    it("should render", () => {
        const wrapper = shallow(
            <Provider store={store}>
                <UserProfileCards items={mockedUserProfiles} />
            </Provider>
        )
        expect(wrapper).toMatchSnapshot()
    })
})

这个测试无法真正验证组件的行为。它检查的是结构,而非用户真实体验的内容。

实现耦合:虚假通过的陷阱

与内部实现细节绑定的测试会带来双重问题:重构时容易失败,同时还会掩盖真正的缺陷。

Google AdInline article slot

以一个 CollapseIcon 组件为例,它根据 opened 状态切换图标状态:

import CollapseIcon from "./CollapseIcon"
import { shallow } from "enzyme"

describe("CollapseIcon", () => {
    it("should render", () => {
        const wrapper = shallow(<CollapseIcon opened={false} />)
        expect(wrapper).toMatchSnapshot()
    })

    it("should react to closed", () => {
        const wrapper = shallow(<CollapseIcon opened={false} />)
        expect(wrapper.find(".collapse__icon").hasClass("collapseIcon_closed")).toEqual(true)
    })

    it("should react to opened", () => {
        const wrapper = shallow(<CollapseIcon opened={true} />)
        expect(wrapper.find(".collapse__icon").hasClass("collapseIcon_opened")).toEqual(true)
    })
})

存在的问题:

  • 快照无法检测动画逻辑错误。
  • 类名检查在样式重构后立即崩溃。
  • 即使动画完全失效,测试仍可能通过。

另一个案例:过度依赖本地化常量:

Google AdInline article slot
it("should render no-answers button", () => {
    const button = getButton(mockCommentsList[0])
    const noAnswersText = `${mockedTranslation.t(`${tNamespace}no-answers`)}
    expect(button.text()).toBe(noAnswersText)
})

只要 tNamespace 中的键名变动,测试就会失败——即使按钮文字显示正确。解决方案:将常量提取到共享模块,由组件和测试共同导入。

模糊的测试描述让问题雪上加霜:

  • "should show tooltip" —— 没有明确条件。
  • "should render without tooltipLabel" —— 没有说明预期结果。

React Testing Library:测试用户行为

RTL 改变了测试范式:测试模拟真实用户操作,忽略内部实现细节。不访问状态、属性或方法,只关注可见的 UI 表现。

Google AdInline article slot

RTL 的优势

  • 以用户为中心:模拟点击、输入、屏幕阅读器操作等真实交互。
  • 重构安全:独立于类名、ID 或内部状态变化。
  • API 设计清晰render()screen.getByRole()fireEvent.click()waitFor() 等。
  • 支持异步场景findByRole() 适用于数据加载过程。
  • 内置无障碍支持getByLabelText() 确保标签与控件正确关联。

RTL 的设计原则是:测试应反映真实使用场景,而非 DOM 结构细节。

高质量测试 Checklist

编写测试时需注意

  • 明确目的:清楚说明测试目标(例如:“当 isTooltipShow=true 时显示提示框”)。
  • 用户视角:用真实用户的方式操作,如 getByRole('button')fireEvent
  • 测试隔离:对 API 使用 mock,避免测试间相互依赖。
  • 描述完整:使用完整句子,无需代码即可理解。
  • 覆盖全面:包含正常流程、边界情况和错误状态。

审查测试时需确认

  • 目标是否清晰?
  • 是否避免了实现细节(类名、状态)?
  • 测试是否独立?
  • 描述是否具有信息量?
  • 注释是否有助于改进?

团队迁移策略

组织一次迁移工作坊:收集现有问题测试,分析典型案例,逐步转向 RTL。建议:

  • 将培训拆分为 45 分钟小节,搭配实操练习。
  • 采用结对编程方式共同重构测试。
  • 重实践轻理论,强调动手能力。

旧代码迁移:新测试直接使用 RTL 编写,逐步替换旧测试。Airbnb 实践案例:6 周内完成 3,500 个文件的迁移,借助基于 LLM 的自动化流水线,实现 75% 自动化。

核心要点

  • 思维转变:从关注标记结构转向关注用户行为。
  • RTL vs Enzyme:RTL 限制内部访问;Enzyme 则鼓励深入组件内部。
  • Checklist 必须执行:编写与审查阶段都需遵循。
  • 工作坊价值:互动交流胜过单向讲授。
  • 迁移加速:自动化工具提升效率,但理解其理念才是关键。

— Editorial Team

Advertisement 728x90

继续阅读