测试驱动开发(TDD)在React项目里并不是新鲜概念,但真正把“先写测试、再写组件”变成团队习惯的并不多。核心循环Red-Green-Refactor指的是:先写一个会失败的测试(Red),再用最少的代码让测试变绿(Green),最后在不改变行为的前提下重构代码(Refactor)。这种方式能把需求翻译成可执行的断言,让组件从诞生起就带有明确契约。

Red阶段:用测试定义组件行为边界
在Red阶段,我们还不写任何React组件代码,而是先站在“使用者”角度描述这个组件应该做什么。比如要做一个显示用户昵称的UserProfile组件,我们期望传入name属性就能渲染对应文本,传入空值时显示默认提示。这种描述直接变成测试用例,此时运行测试必然失败,因为组件文件还不存在。
使用Jest配合React Testing Library可以很自然地书写这类测试。下面这段测试代码在组件未实现时会报模块找不到的错误,正好对应Red状态。注意我们在测试里用render和screen.getByText来断言输出,而不是去测组件内部状态,这样后续重构DOM结构也不会让测试崩溃。
import { render, screen } from '@testing-library/react';
import UserProfile from './UserProfile';
test('renders name when provided', () => {
render(<UserProfile name="Alice" />);
expect(screen.getByText('Alice')).toBeInTheDocument();
});
test('shows placeholder when name is empty', () => {
render(<UserProfile name="" />);
expect(screen.getByText('匿名用户')).toBeInTheDocument();
});
Red阶段的价值在于强迫开发者想清楚输入输出。很多人在直接写组件时容易陷入“先堆UI再补逻辑”的陷阱,导致校验、异常态被遗漏。而测试先行相当于先画好图纸,后面只需要按图施工。即便需求中途变更,改测试也比在成品组件里翻找隐藏分支更可控。
Green与Refactor:最小实现与安心重构
进入Green阶段,目标不是写出完美组件,而是用最直白的方式让上面两个测试通过。我们可以暂时忽略代码优雅度,甚至允许硬编码,只要测试由红转绿即可。下面这个实现就能让前述测试通过,它没有任何多余逻辑,却已经满足了当前契约。
import React from 'react';
export default function UserProfile({ name }) {
if (!name) {
return <div>匿名用户</div>;
}
return <div>{name}</div>;
}
当测试全绿后,我们才进入Refactor。这时可以引入PropTypes做类型校验,或者把默认文本抽成常量,甚至将组件改为使用memo优化渲染。因为有一层测试保护,任何重构只要跑一遍npm test就能知道是否破坏原有行为。这种安全感是TDD最实在的收益,尤其当组件被多个页面依赖时,改动不再令人提心吊胆。
需要提醒的是,Refactor不只针对组件函数本身。如果测试里出现了大量重复渲染逻辑,也可以提取公共的renderHelper。只要外部断言不变,内部怎么拆都是安全的。很多团队在Green之后跳过Refactor,导致代码虽能跑却越来越臃肿,这其实浪费了TDD提供的免费优化窗口。
在复杂交互组件中落地TDD的实践要点
对于带按钮点击、表单提交的组件,TDD同样适用,只是测试要从“渲染断言”升级到“交互断言”。例如一个搜索框组件,我们先写测试:用户输入文字并点击搜索,应该调用传入的onSearch回调并附带值。这个测试在组件不存在时失败,实现后再补最小逻辑使之通过,最后重构事件绑定方式。
import { render, screen, fireEvent } from '@testing-library/react';
import SearchBox from './SearchBox';
test('calls onSearch with input value', () => {
const fn = jest.fn();
render(<SearchBox onSearch={fn} />);
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'react' } });
fireEvent.click(screen.getByText('搜索'));
expect(fn).toHaveBeenCalledWith('react');
});
复杂组件常犯的错误是把太多逻辑写进useEffect或直接依赖全局状态,导致测试难写。TDD会反向逼迫你做依赖注入:把回调、数据源通过props传入,而非在组件内硬编码请求。这样测试时用jest.fn()替代真实接口,既快又稳。从长远看,这种由测试牵引出来的设计往往比事后补测试的系统更松耦合。
另一个要点是别追求百分之百覆盖率而写无意义的测试。TDD里的测试是“活文档”,只描述重要行为。比如不必测React自身渲染机制,只需测业务规则。当Red-Green-Refactor成为肌肉记忆,你会发现组件接口更干净,回归bug更少,新成员也能通过测试快速理解组件契约。
React_TDDRed-Green-Refactor组件测试修改时间:2026-08-14 16:27:29