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

Testing complex React components with Mocha/Chai

我正在研究 addons for fixed-data-table and its sibling,但我正在努力使用 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)

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

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

  1. 查找包含该单元格值的节点
  2. 在测试单元如何实现时保持无知,例如如果是 divtd
  3. 查看第 1 行中的 element/cell 是否出现在本应位于第 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`);
    }
  });
});

我已将自动生成的 ID 标记添加到 Textcell 并尝试使用 .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>

感谢 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, '()').match(second)).to.not.be.null;
    }
  });
});