首先哀悼Hexo好像被墙了。。。不知道两会之后会不会恢复。

好久没写过一篇像样的技术博客了。。。赶巧最近几天碰到好几个以前碰到过但是没太弄清楚的问题,现在抽时间记一下吧。这篇先记一下今天碰到的关于ReactTestUtils.Simulate的一点小问题。

ReactTestUtils.Simulate

首先ReactTestUtils是一套便于对React进行unit test的工具(React Test Utils,字面意思了。。。),其中官方文档提到Simulate is possibly the single most useful utility in ReactTestUtils.

我测试时用到了如下代码:

被测文件step-input.jsx

1
2
3
4
5
6
//change event handler
changeHandler(name, e) {
const s = {};
s[name] = e.target.value;
this.setState(s);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//lots of Material UI TextField components
<TextField
ref="base"
onChange={this.changeHandler.bind(this, 'base')}
value={this.state.base || ''}
/>
...
<TextField
floatingLabelText="Input Info"
ref={'info' + n}
onChange={this.changeHandler.bind(this, 'info' + n)}
value={this.state['info' + n] || ''}
multiLine={true}
rows={1}
rowsMax={100}
style={broadTFStyle}
/>
...

测试文件部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
'use strict';
jest.dontMock('../jsx/step-input.jsx');
import React from 'react';
import ReactTestUtils from 'react-addons-test-utils';

describe('Test onChange', () => {
//Can not use export default in jsx export when test!
//require test jsx in describe, not outside describe! For bable hoist import before mock.
const { StepInput } = require('../jsx/step-input.jsx');
const stepInput = rtu.renderIntoDocument(<StepInput/>);
const node = stepInput.refs.base;
ReactTestUtils.Simulate.change(node, { target: { value: v } });
...

测试代码中的ReactTestUtils.Simulate.change部分并没能触发onChange事件,从而没调用changeHandler

github上找到了类似issue[RaisedButton] TestUtils.click does not work,看完后仔细思考了一下,发现问题在ReactTestUtils.Simulate官方文档里提到的Simulate an event dispatch on a DOM node,既然是DOM node,我的测试代码中const node = stepInput.refs.base固然有问题了,因为这里的stepInput.refs.base是React Component而不是DOM node。

仿照类似该issue中的做法,通过找React Component下的关键DOM node可以完成。但是其中有意思的地方是issue下几个人的留言,@weeksie用了instance.getDOMNode().firstChild即只找了一层就找到了, 而@omerts用了React.findDOMNode(saveButton).firstChild.firstChild则找了两层。再仔细想,可能跟Material UI Component实现时,关键DOM node(例如此issue里RaisedButton的关键DOM node是<button>)相对于Component顶层DOM node的位置有关。

通过查看自己的页面Elements信息,发现TextField顶层DOM node下的第三个DOM node<input>是我要查找的关键DOM node,即查找方法是node.getDOMNode().childNodes[2]。修改代码后再进行测试,发现部分TextField正确触发了onChange事件,另一部分还是没触发。通过审查页面Elements信息发现,同为Material UI TextField Component,生成的DOM node的结构会有不同的结果,本例中另一种的关键DOM node变成了<textarea>,且在node.getDOMNode().childNodes[2].childNodes[2]的位置。最后经研究发现是Material UI通过各种参数配置(例如floatingLabelText, multiLine等)生成的DOM node结构不一样。

总结

  • ReactTestUtils.Simulate确实便于测试React,但是仅便于直接测试React中的DOM Node
  • 若React Component生成的DOM结构较为固定,也可以使用ReactTestUtils.Simulate
  • 若React Component生成的DOM结构不固定,不建议使用ReactTestUtils.Simulate进行测试,例如Material UI。因为很可能因为调整UI界面造成测试出错,例如仅仅因为给TextField添加了Label,或修改了multiLine属性就造成测试出错,且这种错误具有不确定性,不知道修改某个属性是否会引起其关键DOM node相对顶层DOM node位置的改变。