使用 Mocha/Chai 测试复杂的 React 组件

Posted

技术标签:

【中文标题】使用 Mocha/Chai 测试复杂的 React 组件【英文标题】:Testing complex React components with Mocha/Chai 【发布时间】:2017-04-07 19:30:27 【问题描述】:

我正在为fixed-data-table 和its sibling 开发addons,但我正在努力使用Mocha/Chai 设置来设置测试。我得到的错误是:

 TypeError: obj.indexOf is not a function
      at Assertion.include (node_modules/chai/lib/chai/core/assertions.js:228:45)
      at Assertion.assert (node_modules/chai/lib/chai/utils/addChainableMethod.js:84:49)
      at Context.<anonymous> (test/HOC/addFilter_test.jsx:48:23)

我设法找到的所有教程都显示了如何处理测试琐碎的组件,我相信错误是由于节点是一个复杂的组件造成的。

我的问题:如何制作更复杂的组件?我特别需要一些帮助:

    查找包含该单元格值的节点 在测试单元的实现方式时保持无知,例如如果是 divtd 查看第 1 行中的元素/单元格是否出现在应该在第 2 行中的元素之前或之后(用于验证排序)

背景

核心测试设置:

import React from 'react';
import  describe, it  from 'mocha';
import  Table, Column, Cell  from 'fixed-data-table';
import jsdom from 'jsdom';
import jquery from 'jquery';
import chaiJquery from 'chai-jquery';
import TestUtils from 'react-addons-test-utils';
import chai,  expect  from 'chai';
import Data from '../stub/Data';

// Set up testing environment to run like a browser in the command line
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = global.document.defaultView;
const $ = jquery(global.window);

class Wrapper extends React.Component 
  render() 
    return this.props.children;
  


Wrapper.propTypes = 
  children: React.PropTypes.node,
;

// build 'renderComponent' helper that should render a given react class
function renderComponent(ComponentClass, props) 
  let node = null;
  if (typeof (ComponentClass) === 'function') 
    TestUtils.renderIntoDocument(
      <Wrapper ref=n => (node = n)>
        <ComponentClass ...props />
      </Wrapper>);
   else 
    TestUtils.renderIntoDocument(<ComponentClass ...props ref=n => (node = n) />);
  

  return node;


// Set up chai-jquery
chaiJquery(chai, chai.util, $);

const TextCell = ( rowIndex, columnKey, data ) => (
  <Cell>
    data.getObjectAt(rowIndex)[columnKey]
  </Cell>);

TextCell.propTypes = 
  rowIndex: React.PropTypes.number,
  columnKey: React.PropTypes.string,
    data: React.PropTypes.object,
;

数据存根:

class Data 
  constructor(nrows = 4) 
    this.size = nrows;
  

  getSize() 
    return (this.size);
  

  getObjectAt(index) 
    if (index < 0 || index >= this.size) 
      return (null);
    

    return 
      id: index,
      name: `Test name no $index`,
    ;
  


export default Data;

实际测试:

describe('Basic test', () => 
  it('some test', () => 
    const data = new Data();

    const node = renderComponent(props =>
      (<Table
        rowHeight=50
        headerHeight=50
        height=500
        width=500
        filters= name: '' 
        rowsCount=data.getSize()
        ...props
      >
        <Column
          columnKey="id"
          width=250
          header=<Cell>ID</Cell>
          cell=<TextCell data=data />
        />
        <Column
          columnKey="name"
          width=250
          header=<Cell>Name</Cell>
          cell=<TextCell data=data />
        />
      </Table>));

    for (let i = 0; i < data.getSize(); i += 1) 
      expect(node).to.contains(`Test name no $i`);
    
  );
);

更新

按照建议改为enzyme:

import React from 'react';
import  describe, it  from 'mocha';
import chai,  expect  from 'chai';
import  shallow  from 'enzyme';
import chaiEnzyme from 'chai-enzyme';
import  Table, Column, Cell  from 'fixed-data-table';
import Data from '../stub/Data';

Error.stackTraceLimit = 10;
chai.use(chaiEnzyme);

const TextCell = ( rowIndex, columnKey, data ) => (
  <Cell id=`$rowIndex_$columnKey`>
    data.getObjectAt(rowIndex)[columnKey]
  </Cell>);

TextCell.propTypes = 
  rowIndex: React.PropTypes.number,
  columnKey: React.PropTypes.string,
    data: React.PropTypes.object,
;

describe('Basic test', () => 
  it('some test', () => 
    const data = new Data();

    const node = shallow(<Table
      rowHeight=50
      headerHeight=50
      height=500
      width=500
      filters= name: '' 
      rowsCount=data.getSize()
    >
      <Column
        columnKey="id"
        width=250
        header=<Cell>ID</Cell>
        cell=<TextCell data=data />
      />
      <Column
        columnKey="name"
        width=250
        header=<Cell>Name</Cell>
        cell=<TextCell data=data />
      />
    </Table>);

    for (let i = 0; i < data.getSize(); i += 1) 
      expect(node.find(`#$i_name`)).to.have.length(1, `Can't find cell with id: $i_name`);
    
  );
);

我在Textcell 中添加了一个自动生成的id 标记,并尝试使用.find(`#$i_name`) 找到它,但它没有找到该对象。我已经使用 node.html() 检查了包装元素的输出,它确实包含具有正确 ID 的单元格:

<div class="fixedDataTableCellLayout_wrap1 public_fixedDataTableCell_wrap1" id="0_name">
  <div class="fixedDataTableCellLayout_wrap2 public_fixedDataTableCell_wrap2">
    <div class="fixedDataTableCellLayout_wrap3 public_fixedDataTableCell_wrap3">
      <div class="public_fixedDataTableCell_cellContent">
        Test name no 0
      </div>
    </div>
  </div>
</div>

【问题讨论】:

如果是一个选项,我认为用 Jest 和 Enzyme 做测试更容易(我使用 chai 和 chai-enzyme)...看看我的项目看一个例子...@ 987654325@ @LucasKatayama 听起来像是减少样板数量的好方法——不确定它是否解决了核心问题:如何检查节点树下的数据是否存在? 借助酶,您可以找到所需的节点,然后应用断言。 github.com/airbnb/enzyme/blob/master/docs/api/ReactWrapper/… @LucasKatayama 看起来很有趣。明天测试。关于检查节点顺序的任何想法? 以前从来没有这样做过..我认为一种方法是通过关键道具检查位置...Anotger很酷的想法..酶适用于虚拟dom树..你可以通过以下方式检查真实的dom调用 render.. 或不调用.. 你可以使用 mount.. 挂载一个复杂的容器,或者使用 shallow.. 挂载一个简单的组件,这对于单元测试非常有用 【参考方案1】:

感谢 Lucas Katayama 提出使用 enzyme 的建议。请注意,mount 必须总线化,否则find 将无法识别子元素。

通过找到第一个 id,替换之前的所有内容,然后在剩余部分中搜索下一个 ID,解决了排序问题。

import React from 'react';
import  describe, it, beforeEach  from 'mocha';
import  Table, Column, Cell  from 'fixed-data-table';
import chai,  expect  from 'chai';
import  mount  from 'enzyme';
import chaiEnzyme from 'chai-enzyme';
import Data from '../stub/Data';

Error.stackTraceLimit = 10;
chai.use(chaiEnzyme);

const TextCell = ( rowIndex, columnKey, data ) => (
  <Cell id=`$rowIndex_$columnKey`>
    data.getObjectAt(rowIndex)[columnKey]
  </Cell>);

TextCell.propTypes = 
  rowIndex: React.PropTypes.number,
  columnKey: React.PropTypes.string,
  data: React.PropTypes.instanceOf(Data),
;

describe('Basic test', () => 
  const data = new Data();
  let node;
  beforeEach(() => (
    node = mount(<Table
      rowHeight=50
      headerHeight=50
      height=500
      width=500
      filters= name: '' 
      rowsCount=data.getSize()
    >
      <Column
        columnKey="id"
        width=250
        header=<Cell>ID</Cell>
        cell=<TextCell data=data />
      />
      <Column
        columnKey="name"
        width=250
        header=<Cell>Name</Cell>
        cell=<TextCell data=data />
      />
    </Table>)
  ));

  it('test that cells exist', () => 
    for (let i = 0; i < data.getSize(); i += 1) 
      expect(node.find(`#$i_id`)).to.have.length(1, `Can't find cell with id: $i_id`);
      expect(node.find(`#$i_name`)).to.have.length(1, `Can't find cell with id: $i_name`);
    
  );

  it('sort test', () => 
    const txt = node.html();
    for (let i = 0; i < data.getSize() - 1; i += 1) 
      const first = new RegExp(`.+id="$i_id"(.+)`);
      const second = new RegExp(`id="$i + 1_id"`);
      expect(txt.replace(first, '($1)').match(second)).to.not.be.null;
    
  );
);

【讨论】:

以上是关于使用 Mocha/Chai 测试复杂的 React 组件的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 mocha/chai Error throwing 测试失败了?

使用Mocha / Chai和async / await验证是否抛出异常

markdown Mocha,Chai和Sinon的终极单元测试作弊表

markdown Mocha,Chai和Sinon的终极单元测试作弊表

markdown 节点单元测试备忘单:Mocha,Chai和Sinon

当 app 是 Promise 时启动 mocha chai 测试