首页 > 解决方案 > 使用 Jest 和 Enzyme 测试 React 代码时如何处理 DOM 请求?

问题描述

我有一个从 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、Enzyme 3.3.0 和enzyme-adapter-react-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"在我的快照中看到,但测试似乎找不到它。关于如何更好地实现我的测试或更好地构建我的代码的任何想法?

标签: reactjsjestjsenzyme

解决方案


解决方案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);

请参阅此处的示例,其中ListNavListNavWithMock均已加载。

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

  1. document通过创建一个名为的新模块来抽象出依赖于 api 的代码documentHelper.js
  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以及它返回的内容)。


推荐阅读