reactjs - 使用 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"
在我的快照中看到,但测试似乎找不到它。关于如何更好地实现我的测试或更好地构建我的代码的任何想法?
解决方案
解决方案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);
请参阅此处的示例,其中ListNav
和ListNavWithMock
均已加载。
解决方案2(如果你使用webpack)
document
通过创建一个名为的新模块来抽象出依赖于 api 的代码documentHelper.js
- 在您的组件中,导入
documentHelper
- 在您的单元测试中,使用https://github.com/plasticine/inject-loader
documentHelper
将模块与模拟交换。
例子:
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
以及它返回的内容)。
推荐阅读
- image - Flutter 中的 SimpleDialogOption 文本和图像
- discord-jda - 从消息方法中获取用户
- android - 在android中使用套接字发送和接收数据
- android-fragments - 在 Fragment over Tab 中显示 webview
- android - React Native - 从带有状态栏的屏幕导航到全屏
- java - 如何在 MaterialCalendarView 中显示特定日期和时间的事件?
- c++ - 如何实现非递归填充算法
- javascript - javascript找不到blob
- laravel - Laravel 与日期时间选择器
- command-line - 如何从命令行运行带有程序名但没有路径的python程序?