用Jest和Enzyme测试React代码时如何处理DOM请求?

时间:2018-07-23 17:27:14

标签: reactjs jestjs enzyme

我有一个使用create-react-app(使用react-scripts 1.1.4)创建的React 16应用程序,其中包含我们创建的以下组件:

import React, {Component} from 'react';
import './ListNav.css';

const tabs = {
    previousIndex: 0
};

function styleStringGenerator(index) {
    let styleString = {
        leftBase: 'left: ',
        widthBase: 'width: '
    }

    if (index === 0) {
        styleString.aggregate = `${styleString.leftBase} 0;         ${styleString.widthBase}${tabs.widths[0]}px;`;
} else {
    styleString.aggregate =     `${styleString.leftBase}${tabs.distanceFromOrigin[index]}px;     ${styleString.widthBase}${tabs.widths[index]}px;`;
}

    return styleString.aggregate;
}  

class ListNav extends Component{
    constructor(props){
        super(props);
        this.handleDataTypeSelection =     this.handleDataTypeSelection.bind(this);

        this.tabScrollWidth = null;

        this.setInputRef = element => {
            this.tabScrollWidth = element;
        };
    }

    render(){
        const dataTypeSelection = (s) => () => this.handleDataTypeSelection(s);

        return(
            <div className="tab" ref={this.setInputRef}>
                <div className="tab__header" onClick={dataTypeSelection("Addresses")}>
                    <span className="tab__title">Addresses</span>
                </div>
                <div className="tab__header" onClick={dataTypeSelection("Hotspots")}>
                    <span className="tab__title">Hotspot Data</span>
                </div>
                <div className="tab__header" onClick={dataTypeSelection("PSRs")}>
                    <span className="tab__title">PSRs</span>
                </div>
                <div className="tab__underline"></div>
            </div>
        );
    }

    componentDidMount(){
        tabs.elements = document.querySelectorAll('.tab__header');
        tabs.length = tabs.elements.length;
        tabs.finalIndex = tabs.length - 1;
        tabs.totalWidth = document.querySelector('.tab').scrollWidth;
        console.log(document);

        tabs.widths = []
        tabs.elements.forEach((v, index, array) => {
            tabs.widths.push(v.scrollWidth);
        });

        tabs.distanceFromOrigin = [0];
        tabs.widths.forEach((v, index, array) => {
            if (index > 0) {
                tabs.distanceFromOrigin.push(array[index-1] + tabs.distanceFromOrigin[index-1]);
            }
        });

        let styleString = styleStringGenerator(0);
                document.querySelector('.tab__underline').setAttribute('style', styleString);
        document.querySelector('.tab__title').setAttribute('class', 'tab__title tab__title--active');    

        document.querySelectorAll('.tab__header').forEach((v, index, array) => v.addEventListener('click', function(){
            const currentIndex = index;

            if (tabs.previousIndex !== currentIndex) {

                const styleString = styleStringGenerator(index);

                document.querySelector('.tab__underline').setAttribute('style', styleString);
                document.querySelector('.tab__title--active').setAttribute('class', 'tab__title');
                this.querySelector('.tab__title').setAttribute('class', 'tab__title tab__title--active');

                tabs.previousIndex = (function(){return currentIndex})();
            }

        }, index));        
    }

    handleDataTypeSelection(s){
        this.props.getData(s);
    }
}

export default ListNav;

我使用的是Jest 20.0.4,酶3.3.0和酶适配器反应16 1.1.1,并创建了以下测试:

import React from 'react';
import Enzyme from 'enzyme';
import {shallow, mount} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

import ListNav from '../components/map-list/list-nav/ListNav';

Enzyme.configure({
  adapter: new Adapter()
});

const listNav = shallow(<ListNav/>);

describe('ListNav', () => {
  it('ListNav renders without crashing', () => {
    expect(listNav).toMatchSnapshot(); 
  });
});

运行测试时,出现以下错误:

  

TypeError:无法读取null的属性“ scrollWidth”

有问题的行在组件中的componentDidMount()调用中。代码在以下行失败:

tabs.totalWidth = document.querySelector('.tab').scrollWidth;

因为tabs.totalWidth = document.querySelector('.tab')的值为null,所以无法读取scrollWidth。我正在使用shallow(<ListNav/>),可以在快照中看到"classname": "tab",但是测试似乎找不到它。关于如何更好地实施测试或更好地构建代码的任何想法?

1 个答案:

答案 0 :(得分:0)

解决方案1:

使用闭包使您的document依赖性可交换。这样,您可以在单元测试中提供一个模拟。

实际代码中的用法是:

import ListNav from "./ListNav";
...
render(){
  return <ListNav/>;
}

在测试中的用途:

import { create } from "./ListNav";

it('should...', ()=>{
  const documentMock = { title: "mock title" };
  const ListNavWithMock = create(documentMock);
  const component = shallow(<ListNavWithMock />);
});

为了支持必须对模块进行如下修改:

import React from "react";
export const create = documentInstance => {
  return class ListNav extends React.Component {
    render() {
      return <div>{documentInstance.title}</div>;
    }
  };
};

export default create(document);

查看exemple here,其中同时加载了ListNavListNavWithMock

解决方案2(如果使用webpack)

  1. 通过创建名为document的新模块来提取依赖于documentHelper.js api的代码
  2. 在您的组件中,导入documentHelper
  3. 在单元测试中,使用https://github.com/plasticine/inject-loaderdocumentHelper模块与模拟模块交换。

示例:

describe('ListNav', () => {
  let ListNav ;
  let documentHelperMock;

  beforeEach(() => {
    documentHelperMock= { title: "mock title" };
    ListNav= require('inject-loader!./ListNav')({
      '.documentHelperMock': {documentHelperMock},
    });
  });

  it('should ...', () => {
    const wrapper = shallow(<ListNav/>)
  });
});

注意:确保没有在文件顶部导入被测模块(ListNav)。 require调用完成了这一部分。

此方法较少侵入性,因为不必以明显的方式修改组件代码以用于测试的目的。通过将文档特定的代码移出组件,这只会使代码更整洁。

此方法也更容易,因为您必须模拟的API是您自己的(documentHelper.UpdateTabs)。在第一种解决方案中,您的模拟可能必须很复杂(querySelector及其返回值)。