简体   繁体   English

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

[英]How do I deal with DOM requests when testing React code with Jest and Enzyme?

I have a React 16 application created from create-react-app (which uses react-scripts 1.1.4) with the following component we created: 我有一个使用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;

I am using Jest 20.0.4, Enzyme 3.3.0 and enzyme-adapter-react-16 1.1.1 and created the following test: 我正在使用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(); 
  });
});

When I run my test, I get the following error: 运行测试时,出现以下错误:

TypeError: Cannot read property 'scrollWidth' of null TypeError:无法读取null的属性'scrollWidth'

The line in question is in the component, in the componentDidMount() call. 有问题的行在组件的componentDidMount()调用中。 The code fails on the line: 代码在行上失败:

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

because tabs.totalWidth = document.querySelector('.tab') evaluates to null so scrollWidth can't be read. 因为tabs.totalWidth = document.querySelector('.tab')计算结果为null,所以无法读取scrollWidth I am using shallow(<ListNav/>) and can see "classname": "tab" in my snapshot, but the test cannot seem to find it. 我正在使用shallow(<ListNav/>)并且可以在快照中看到"classname": "tab" ,但是测试似乎找不到它。 Any ideas as to how to either better implement my test or better construct my code? 关于如何更好地实施测试或更好地构建代码的任何想法?

Solution 1: 解决方案1:

Make your document dependency swappable using a closure. 使用闭包使document依赖项可交换 This way, in your unit tests you can provide a mock. 这样,您可以在单元测试中提供一个模拟。

Usage in your real code would be: 在实际代码中的用法是:

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

Usage in your tests: 测试中的用法:

import { create } from "./ListNav";

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

In order to support that your module will have to be modified like this: 为了支持您的模块将必须像这样修改:

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

export default create(document);

See an exemple here where both ListNav and ListNavWithMock are loaded. 在此处查看ListNav ,其中同时加载了ListNavListNavWithMock

Solution 2 (if you use webpack) 解决方案2(如果使用webpack)

  1. Abstract away the code that relies on the document api by creating a new module called documentHelper.js 通过创建一个名为documentHelper.js的新模块,抽象出依赖于document api的代码
  2. In your component, import documentHelper 在您的组件中,导入documentHelper
  3. In your unit test, swap the documentHelper module with a mock using https://github.com/plasticine/inject-loader . 在单元测试中,使用https://github.com/plasticine/inject-loader将模拟documentHelper交换documentHelper模块。

Example: 例:

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

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

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

Note: make sure you don't import the module under test ( ListNav ) at the top of your file. 注意:请确保不要在文件顶部导入被测模块( ListNav )。 The require call does that part. require调用完成了这一部分。

This approach is less intrusive because component code does not have to be modified in a way that makes it obvious that it's for testing purpose. 这种方法具有较低的介入性,因为不必以明显使其用于测试目的的方式修改组件代码。 It just makes the code cleaner by moving document specific code out of your component. 它只是通过将文档特定的代码移出组件来使代码更整洁。

This approach is also easier because the APIs you will have to mock will be your own ( documentHelper.UpdateTabs ). 这种方法也更容易,因为您必须模拟的API是您自己的( documentHelper.UpdateTabs )。 In the first solution your mock might have to be complex ( querySelector and what it returns). 在第一个解决方案中,您的模拟可能必须很复杂( querySelector及其返回的内容)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM