匠人学院 JR Academy学AI来匠人
匠人学院 JR Academy学AI来匠人

Follow Us

linkedinfacebooktwitterinstagramweiboyoutubebilibilitiktokxigua

We Accept

/image/layout/pay-paypal.png/image/layout/pay-visa.png/image/layout/pay-master-card.png/image/layout/pay-airwallex.png/image/layout/pay-alipay.png
EN

关于公司

关于我们元宇宙课堂新闻资讯匠人工作成为导师匠人导师联系我们匠人商店J3.Club

匠人资源

工作内推匠人活动1对1私教行业白皮书线上学习平台面试中心分享面试经验Internship会员中心

AI 工具

AI 工具箱考证匠 Cert Master求职匠 Job Hunter牛小匠 UniMate AI

AI 学习方向

全部学习方向AI EngineerContext EngineeringVibe CodingPrompt MasterAI BuilderAI 产品经理Python 入门

AI 应用提效

AI 办公提效AI 数据分析AI 财务AI 内容创作AI 视觉创作前端开发Hermes AgentOpenClaw 本地智能体

大学资源

墨尔本大学昆士兰大学新南威尔士大学悉尼大学莫那什大学阿德莱德大学RMITQUTUTS

少儿 AI 教育

Airbotix 少儿 AI 编程澳洲家长实用资料库NAPLAN 成绩单怎么看My School 学校数据指南悉尼私校学费 2026少儿编程课程与训练营

移民服务

澳洲移民技术移民189/190/491雇主担保482/186/494投资移民188/888英国移民美国移民加拿大移民

企业合作

P3职业孵化器Enterprise (EN)企业培训实习合作招聘合作申请合作

求职代理

岗位代投职位监控LinkedIn代运营LinkedIn人脉代加了解P3项目

匠人支持

FAQsTerms & ConditionsPrivacy PolicyCancellation & Refund PolicySite map

Top Categories

Web全栈班DevOps项目班数据工程全栈班数据分析项目班编程入门班Business Analyst实习算法集训营

求职就业

BA和产品经理实习数据科学实习数据分析实习Marketing实习简历修改面试指导导师指导VIP

地址

Level 10b, 144 Edward Street, Brisbane CBD(Headquarter)
Level 2, 171 La Trobe St, Melbourne VIC 3000
四川省成都市武侯区桂溪街道天府大道中段500号D5东方希望天祥广场B座45A13号
Business Hub, 155 Waymouth St, Adelaide SA 5000

联系方式

hello@jiangren.com.au0421-672-555

Disclaimer

footer-disclaimerfooter-disclaimer

JR Academy acknowledges Traditional Owners of Country throughout Australia and recognises the continuing connection to lands, waters and communities. We pay our respect to Aboriginal and Torres Strait Islander cultures; and to Elders past and present. Aboriginal and Torres Strait Islander peoples should be aware that this website may contain images or names of people who have since passed away.

匠人学院网站上的所有内容,包括课程材料、徽标和匠人学院网站上提供的信息,均受澳大利亚政府知识产权法的保护。严禁未经授权使用、销售、分发、复制或修改。违规行为可能会导致法律诉讼。通过访问我们的网站,您同意尊重我们的知识产权。JR Academy Pty Ltd 保留所有权利,包括专利、商标和版权。任何侵权行为都将受到法律追究。查看用户协议

© 2017-2026 JR Academy Pty Ltd. All rights reserved.

ABN 26621887572

首页/资源中心/文章详情
JR Academy · Blog职业洞察

React 测试入门教程

越来越多的人,使用React开发Web应用。它的测试就成了一个大问题。React的组件结构和JSX语法,不适用传统的测试工具,必须有新的测试方法和工具。本文总结目前React测试的基本做法和最佳实践,手把手教你如何写React测试。

发布日期2020-06-24
阅读时长7 分钟
作者Ann Ann

快速导航

  • 一、Demo应用
  • 二、测试工具库
  • 三、官方测试工具库
  • 3.1 Shallow Rendering
  • 3.2 renderIntoDocument
  • 3.3 findDOMNode
  • 四、Enzyme库
  • 4.1 shallow
  • 4.2 render
  • 4.3 mount
  • 4.4 API

一、Demo应用

请先安装Demo。

$ git clone https://github.com/ruanyf/react-testing-demo.git
$ cd react-testing-demo && npm install
$ npm start

然后,打开 http://127.0.0.1:8080/,你会看到一个 Todo 应用。

接下来,我们就要测试这个应用,一共有5个测试点。

  1. 应用标题应为“Todos”
  2. Todo项的初始状态(“未完成”或“已完成”)应该正确
  3. 点击一个Todo项,它就反转状态(“未完成”变为“已完成”,反之亦然)
  4. 点击删除按钮,该Todo项就被删除
  5. 点击添加按钮,会新增一个Todo项

这5个测试用例都已经写好了,执行一下就可以看到结果。

$ npm test

下面就来看,测试用例应该怎么写。测试框架我用的是Mocha,如果你不熟悉,可以先读我写的《Mocha教程》。

二、测试工具库

React测试必须使用官方的测试工具库,但是它用起来不够方便,所以有人做了封装,推出了一些第三方库,其中Airbnb公司的Enzyme最容易上手。

这就是说,同样的测试用例至少有两种写法,本文都将介绍。

  • 官方测试工具库的写法
  • Enzyme的写法

三、官方测试工具库

我们知道,一个React组件有两种存在形式:虚拟DOM对象(即React.Component的实例)和真实DOM节点。官方测试工具库对这两种形式,都提供测试解决方案。

  • Shallow Rendering:测试虚拟DOM的方法
  • DOM Rendering: 测试真实DOM的方法

3.1 Shallow Rendering

Shallow Rendering (浅渲染)指的是,将一个组件渲染成虚拟DOM对象,但是只渲染第一层,不渲染所有子组件,所以处理速度非常快。它不需要DOM环境,因为根本加载进DOM。

首先,在测试脚本之中,引入官方测试工具库。

import TestUtils from 'react-addons-test-utils';

然后,写一个 Shallow Rendering 函数,该函数返回的就是一个浅渲染的虚拟DOM对象。

import TestUtils from 'react-addons-test-utils';

function shallowRender(Component) {
  const renderer = TestUtils.createRenderer();
  renderer.render(<Component/>);
  return renderer.getRenderOutput();
}

第一个测试用例,是测试标题是否正确。这个用例不需要与DOM互动,不涉及子组件,所以使用浅渲染非常合适。

describe('Shallow Rendering', function () {
  it('App\'s title should be Todos', function () {
    const app = shallowRender(App);
    expect(app.props.children[0].type).to.equal('h1');
    expect(app.props.children[0].props.children).to.equal('Todos');
  });
});

上面代码中,const app = shallowRender(App)表示对App组件进行“浅渲染”,然后app.props.children[0].props.children就是组件的标题。

你大概会觉得,这个属性的写法太古怪了,但实际上是有规律的。每一个虚拟DOM对象都有props.children属性,它包含一个数组,里面是所有的子组件。app.props.children[0]就是第一个子组件,在我们的例子中就是h1元素,它的props.children属性就是h1的文本。

第二个测试用例,是测试Todo项的初始状态。

首先,需要修改shallowRender函数,让它接受第二个参数。

import TestUtils from 'react-addons-test-utils';

function shallowRender(Component, props) {
  const renderer = TestUtils.createRenderer();
  renderer.render(<Component {...props}/>);
  return renderer.getRenderOutput();
}

下面就是测试用例。

import TodoItem from '../app/components/TodoItem';

describe('Shallow Rendering', function () {
  it('Todo item should not have todo-done class', function () {
    const todoItemData = { id: 0, name: 'Todo one', done: false };
    const todoItem = shallowRender(TodoItem, {todo: todoItemData});
    expect(todoItem.props.children[0].props.className.indexOf('todo-done')).to.equal(-1);
  });
});

上面代码中,由于TodoItem是App的子组件,所以浅渲染必须在TodoItem上调用,否则渲染不出来。在我们的例子中,初始状态反映在组件的Class属性(props.className)是否包含todo-done。

3.2 renderIntoDocument

官方测试工具库的第二种测试方法,是将组件渲染成真实的DOM节点,再进行测试。这时就需要调用renderIntoDocument 方法。

import TestUtils from 'react-addons-test-utils';
import App from '../app/components/App';

const app = TestUtils.renderIntoDocument(<App/>);

renderIntoDocument 方法要求存在一个真实的DOM环境,否则会报错。因此,测试用例之中,DOM环境(即window, document 和 navigator 对象)必须是存在的。jsdom 库提供这项功能。

import jsdom from 'jsdom';

if (typeof document === 'undefined') {
  global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
  global.window = document.defaultView;
  global.navigator = global.window.navigator;
}

将上面这段代码,保存在test子目录下,取名为 setup.js。然后,修改package.json。

{
  "scripts": {
    "test": "mocha --compilers js:babel-core/register --require ./test/setup.js",
  },
}

现在,每次运行npm test,setup.js 就会包含在测试脚本之中一起执行。

第三个测试用例,是测试删除按钮。

describe('DOM Rendering', function () {
  it('Click the delete button, the Todo item should be deleted', function () {
    const app = TestUtils.renderIntoDocument(<App/>);
    let todoItems = TestUtils.scryRenderedDOMComponentsWithTag(app, 'li');
    let todoLength = todoItems.length;
    let deleteButton = todoItems[0].querySelector('button');
    TestUtils.Simulate.click(deleteButton);
    let todoItemsAfterClick = TestUtils.scryRenderedDOMComponentsWithTag(app, 'li');
    expect(todoItemsAfterClick.length).to.equal(todoLength - 1);
  });
});

上面代码中,第一步是将App渲染成真实的DOM节点,然后使用scryRenderedDOMComponentsWithTag方法找出app里面所有的li元素。然后,取出第一个li元素里面的button元素,使用TestUtils.Simulate.click方法在该元素上模拟用户点击。最后,判断剩下的li元素应该少了一个。

这种测试方法的基本思路,就是找到目标节点,然后触发某种动作。官方测试工具库提供多种方法,帮助用户找到所需的DOM节点。

  • scryRenderedDOMComponentsWithClass:找出所有匹配指定className的节点
  • findRenderedDOMComponentWithClass:与scryRenderedDOMComponentsWithClass用法相同,但只返回一个节点,如有零个或多个匹配的节点就报错
  • scryRenderedDOMComponentsWithTag:找出所有匹配指定标签的节点
  • findRenderedDOMComponentWithTag:与scryRenderedDOMComponentsWithTag用法相同,但只返回一个节点,如有零个或多个匹配的节点就报错
  • scryRenderedComponentsWithType:找出所有符合指定子组件的节点
  • findRenderedComponentWithType:与scryRenderedComponentsWithType用法相同,但只返回一个节点,如有零个或多个匹配的节点就报错
  • findAllInRenderedTree:遍历当前组件所有的节点,只返回那些符合条件的节点

可以看到,上面这些方法很难拼写,好在还有另一种找到DOM节点的替代方法。

3.3 findDOMNode

如果一个组件已经加载进入DOM,react-dom模块的findDOMNode方法会返回该组件所对应的DOM节点。

我们使用这种方法来写第四个测试用例,用户点击Todo项时的行为。

import {findDOMNode} from 'react-dom';

describe('DOM Rendering', function (done) {
  it('When click the Todo item,it should become done', function () {
    const app = TestUtils.renderIntoDocument(<App/>);
    const appDOM = findDOMNode(app);
    const todoItem = appDOM.querySelector('li:first-child span');
    let isDone = todoItem.classList.contains('todo-done');
    TestUtils.Simulate.click(todoItem);
    expect(todoItem.classList.contains('todo-done')).to.be.equal(!isDone);
  });
});

上面代码中,findDOMNode方法返回App所在的DOM节点,然后找出第一个li节点,在它上面模拟用户点击。最后,判断classList属性里面的todo-done,是否出现或消失。

第五个测试用例,是添加新的Todo项。

describe('DOM Rendering', function (done) {
  it('Add an new Todo item, when click the new todo button', function () {
    const app = TestUtils.renderIntoDocument(<App/>);
    const appDOM = findDOMNode(app);
    let todoItemsLength = appDOM.querySelectorAll('.todo-text').length;
    let addInput = appDOM.querySelector('input');
    addInput.value = 'Todo four';
    let addButton = appDOM.querySelector('.add-todo button');
    TestUtils.Simulate.click(addButton);
    expect(appDOM.querySelectorAll('.todo-text').length).to.be.equal(todoItemsLength + 1);
  });
});

上面代码中,先找到input输入框,添加一个值。然后,找到Add Todo按钮,在它上面模拟用户点击。最后,判断新的Todo项是否出现在Todo列表之中。

findDOMNode方法的最大优点,就是支持复杂的CSS选择器。这是TestUtils本身不提供的。

四、Enzyme库

Enzyme是官方测试工具库的封装,它模拟了jQuery的API,非常直观,易于使用和学习。

它提供三种测试方法。

  • shallow
  • render
  • mount

4.1 shallow

shallow方法就是官方的shallow rendering的封装。

下面是第一个测试用例,测试App的标题。

import {shallow} from 'enzyme';

describe('Enzyme Shallow', function () {
  it('App\'s title should be Todos', function () {
    let app = shallow(<App/>);
    expect(app.find('h1').text()).to.equal('Todos');
  });
};

上面代码中,shallow方法返回App的浅渲染,然后app.find方法找出h1元素,text方法取出该元素的文本。

关于find方法,有一个注意点,就是它只支持简单选择器,稍微复杂的一点的CSS选择器都不支持。

component.find('.my-class'); // by class name
component.find('#my-id'); // by id
component.find('td'); // by tag
component.find('div.custom-class'); // by compound selector
component.find(TableRow); // by constructor
component.find('TableRow'); // by display name

4.2 render

render方法将React组件渲染成静态的HTML字符串,然后分析这段HTML代码的结构,返回一个对象。它跟shallow方法非常像,主要的不同是采用了第三方HTML解析库Cheerio,它返回的是一个Cheerio实例对象。

下面是第二个测试用例,测试所有Todo项的初始状态。

import {render} from 'enzyme';

describe('Enzyme Render', function () {
  it('Todo item should not have todo-done class', function () {
    let app = render(<App/>);
    expect(app.find('.todo-done').length).to.equal(0);
  });
});

在上面代码中,你可以看到,render方法与shallow方法的API基本是一致的。 Enzyme的设计就是,让不同的底层处理引擎,都具有同样的API(比如find方法)。

4.3 mount

mount方法用于将React组件加载为真实DOM节点。

下面是第三个测试用例,测试删除按钮。

import {mount} from 'enzyme';

describe('Enzyme Mount', function () {
  it('Delete Todo', function () {
    let app = mount(<App/>);
    let todoLength = app.find('li').length;
    app.find('button.delete').at(0).simulate('click');
    expect(app.find('li').length).to.equal(todoLength - 1);
  });
});

上面代码中,find方法返回一个对象,包含了所有符合条件的子组件。在它的基础上,at方法返回指定位置的子组件,simulate方法就在这个组件上触发某种行为。

下面是第四个测试用例,测试Todo项的点击行为。

import {mount} from 'enzyme';

describe('Enzyme Mount', function () {
  it('Turning a Todo item into Done', function () {
    let app = mount(<App/>);
    let todoItem = app.find('.todo-text').at(0);
    todoItem.simulate('click');
    expect(todoItem.hasClass('todo-done')).to.equal(true);
  });
});

下面是第五个测试用例,测试添加新的Todo项。

import {mount} from 'enzyme';

describe('Enzyme Mount', function () {
  it('Add a new Todo', function () {
    let app = mount(<App/>);
    let todoLength = app.find('li').length;
    let addInput = app.find('input').get(0);
    addInput.value = 'Todo Four';
    app.find('.add-button').simulate('click');
    expect(app.find('li').length).to.equal(todoLength + 1);
  });
});

4.4 API

下面是Enzyme的一部分API,你可以从中了解它的大概用法。

  • .get(index):返回指定位置的子组件的DOM节点
  • .at(index):返回指定位置的子组件
  • .first():返回第一个子组件
  • .last():返回最后一个子组件
  • .type():返回当前组件的类型
  • .text():返回当前组件的文本内容
  • .html():返回当前组件的HTML代码形式
  • .props():返回根组件的所有属性
  • .prop(key):返回根组件的指定属性
  • .state([key]):返回根组件的状态
  • .setState(nextState):设置根组件的状态
  • .setProps(nextProps):设置根组件的属性
News
作者Ann Ann
一键分享或复制链接
Ann Ann
Reviewer: Ann Ann

Founder of JR Academy

查看该作者的更多文章 →

相关学习资源

  • 前端学习路径
← 上一篇Git 协作流程下一篇 →IaaS,PaaS,SaaS 的区别

相关文章推荐

什么!一站式BusinessAnalyst训练营+实习又来了?快来抢占职场先机!

2024-08-30

精彩回顾|AI与创新激情碰撞!悉尼JR&AtlassianAI主题活动完美收官!

2024-08-26

官宣!澳洲政府颁布新规!留学生或将被限制入境,485确定免费延长2年!

2024-08-21

平心而论,我在澳洲没见过这么好的数据分析班!

2024-08-20

澳洲数据分析+任何专业=王炸!

2024-08-20

年薪可达10万澳币?这“十大”工作成为华人梦中情职!就业季来袭,墨尔本职业博览会强势开启,500+岗位为你职业生涯保驾护航!

2024-08-14
查看全部文章 →
JR Academy
全球华人学习 AI 第一站
✓15000+ 学员
✓50+ 课程
✓AI 驱动学习平台
训练营免费资源AI学习职业辅导
精选推荐
AI 职业影响地图
测测你的职业风险等级,查看转型路径与学习方向
热门工具
AI & 数据训练营
系统化课程 + 真实项目实战,快速提升竞争力
热门课程
1v1 就业辅导
资深导师一对一指导,简历优化 + 面试准备
就业保障
企业内训定制
AI 技能培训方案,助力团队升级
企业服务
热门标签
Vibe CodingAI 编程CursorClaude求职攻略Prompt前端开发后端开发
订阅更新

获取最新 AI 学习资源、技术教程和求职攻略,直接送达邮箱。

我们尊重您的隐私,不会发送垃圾邮件