How do I deal with DOM requests when testing React code with Jest and Enzyme?
up vote
1
down vote
favorite
I have a React 16 application created from create-react-app (which uses react-scripts 1.1.4) with the following component we created:
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:
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
The line in question is in the component, in the componentDidMount()
call. 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. I am using shallow(<ListNav/>)
and can see "classname": "tab"
in my snapshot, but the test cannot seem to find it. Any ideas as to how to either better implement my test or better construct my code?
reactjs jestjs enzyme
add a comment |
up vote
1
down vote
favorite
I have a React 16 application created from create-react-app (which uses react-scripts 1.1.4) with the following component we created:
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:
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
The line in question is in the component, in the componentDidMount()
call. 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. I am using shallow(<ListNav/>)
and can see "classname": "tab"
in my snapshot, but the test cannot seem to find it. Any ideas as to how to either better implement my test or better construct my code?
reactjs jestjs enzyme
Have you tried usingmount
instead of shallow? Generally I feel like enzyme works better when you use the methods they've provided you, like.find()
– tenor528
Jul 23 at 17:55
I did trymount()
but got the same result.
– TortillaCurtain
Jul 23 at 18:00
add a comment |
up vote
1
down vote
favorite
up vote
1
down vote
favorite
I have a React 16 application created from create-react-app (which uses react-scripts 1.1.4) with the following component we created:
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:
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
The line in question is in the component, in the componentDidMount()
call. 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. I am using shallow(<ListNav/>)
and can see "classname": "tab"
in my snapshot, but the test cannot seem to find it. Any ideas as to how to either better implement my test or better construct my code?
reactjs jestjs 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:
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:
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
The line in question is in the component, in the componentDidMount()
call. 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. I am using shallow(<ListNav/>)
and can see "classname": "tab"
in my snapshot, but the test cannot seem to find it. Any ideas as to how to either better implement my test or better construct my code?
reactjs jestjs enzyme
reactjs jestjs enzyme
edited Nov 7 at 7:52
skyboyer
2,69511027
2,69511027
asked Jul 23 at 17:27
TortillaCurtain
1559
1559
Have you tried usingmount
instead of shallow? Generally I feel like enzyme works better when you use the methods they've provided you, like.find()
– tenor528
Jul 23 at 17:55
I did trymount()
but got the same result.
– TortillaCurtain
Jul 23 at 18:00
add a comment |
Have you tried usingmount
instead of shallow? Generally I feel like enzyme works better when you use the methods they've provided you, like.find()
– tenor528
Jul 23 at 17:55
I did trymount()
but got the same result.
– TortillaCurtain
Jul 23 at 18:00
Have you tried using
mount
instead of shallow? Generally I feel like enzyme works better when you use the methods they've provided you, like .find()
– tenor528
Jul 23 at 17:55
Have you tried using
mount
instead of shallow? Generally I feel like enzyme works better when you use the methods they've provided you, like .find()
– tenor528
Jul 23 at 17:55
I did try
mount()
but got the same result.– TortillaCurtain
Jul 23 at 18:00
I did try
mount()
but got the same result.– TortillaCurtain
Jul 23 at 18:00
add a comment |
1 Answer
1
active
oldest
votes
up vote
0
down vote
Solution 1:
Make your document
dependency swappable using a closure. 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.
Solution 2 (if you use webpack)
- Abstract away the code that relies on the
document
api by creating a new module calleddocumentHelper.js
- In your component, import
documentHelper
- In your unit test, swap the
documentHelper
module with a mock using https://github.com/plasticine/inject-loader.
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. The require
call does that part.
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
). In the first solution your mock might have to be complex (querySelector
and what it returns).
Thank you for your very complete example. I am using Webpack, but via create-react-app. I tried your second approach but could not get inject-loader to work. I always received a Cannot find module error despite repeated attempts. I found that if I used refs to pass values, I could change my code to use them rather thandocument
calls. I'm not sure if this is the best approach, but it solved my problem. I will pursue your Solution 1 as well to see if it works for me.
– TortillaCurtain
Jul 26 at 14:59
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
0
down vote
Solution 1:
Make your document
dependency swappable using a closure. 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.
Solution 2 (if you use webpack)
- Abstract away the code that relies on the
document
api by creating a new module calleddocumentHelper.js
- In your component, import
documentHelper
- In your unit test, swap the
documentHelper
module with a mock using https://github.com/plasticine/inject-loader.
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. The require
call does that part.
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
). In the first solution your mock might have to be complex (querySelector
and what it returns).
Thank you for your very complete example. I am using Webpack, but via create-react-app. I tried your second approach but could not get inject-loader to work. I always received a Cannot find module error despite repeated attempts. I found that if I used refs to pass values, I could change my code to use them rather thandocument
calls. I'm not sure if this is the best approach, but it solved my problem. I will pursue your Solution 1 as well to see if it works for me.
– TortillaCurtain
Jul 26 at 14:59
add a comment |
up vote
0
down vote
Solution 1:
Make your document
dependency swappable using a closure. 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.
Solution 2 (if you use webpack)
- Abstract away the code that relies on the
document
api by creating a new module calleddocumentHelper.js
- In your component, import
documentHelper
- In your unit test, swap the
documentHelper
module with a mock using https://github.com/plasticine/inject-loader.
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. The require
call does that part.
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
). In the first solution your mock might have to be complex (querySelector
and what it returns).
Thank you for your very complete example. I am using Webpack, but via create-react-app. I tried your second approach but could not get inject-loader to work. I always received a Cannot find module error despite repeated attempts. I found that if I used refs to pass values, I could change my code to use them rather thandocument
calls. I'm not sure if this is the best approach, but it solved my problem. I will pursue your Solution 1 as well to see if it works for me.
– TortillaCurtain
Jul 26 at 14:59
add a comment |
up vote
0
down vote
up vote
0
down vote
Solution 1:
Make your document
dependency swappable using a closure. 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.
Solution 2 (if you use webpack)
- Abstract away the code that relies on the
document
api by creating a new module calleddocumentHelper.js
- In your component, import
documentHelper
- In your unit test, swap the
documentHelper
module with a mock using https://github.com/plasticine/inject-loader.
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. The require
call does that part.
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
). In the first solution your mock might have to be complex (querySelector
and what it returns).
Solution 1:
Make your document
dependency swappable using a closure. 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.
Solution 2 (if you use webpack)
- Abstract away the code that relies on the
document
api by creating a new module calleddocumentHelper.js
- In your component, import
documentHelper
- In your unit test, swap the
documentHelper
module with a mock using https://github.com/plasticine/inject-loader.
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. The require
call does that part.
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
). In the first solution your mock might have to be complex (querySelector
and what it returns).
edited Jul 26 at 17:13
answered Jul 25 at 2:44
Sylvain
11.3k2285129
11.3k2285129
Thank you for your very complete example. I am using Webpack, but via create-react-app. I tried your second approach but could not get inject-loader to work. I always received a Cannot find module error despite repeated attempts. I found that if I used refs to pass values, I could change my code to use them rather thandocument
calls. I'm not sure if this is the best approach, but it solved my problem. I will pursue your Solution 1 as well to see if it works for me.
– TortillaCurtain
Jul 26 at 14:59
add a comment |
Thank you for your very complete example. I am using Webpack, but via create-react-app. I tried your second approach but could not get inject-loader to work. I always received a Cannot find module error despite repeated attempts. I found that if I used refs to pass values, I could change my code to use them rather thandocument
calls. I'm not sure if this is the best approach, but it solved my problem. I will pursue your Solution 1 as well to see if it works for me.
– TortillaCurtain
Jul 26 at 14:59
Thank you for your very complete example. I am using Webpack, but via create-react-app. I tried your second approach but could not get inject-loader to work. I always received a Cannot find module error despite repeated attempts. I found that if I used refs to pass values, I could change my code to use them rather than
document
calls. I'm not sure if this is the best approach, but it solved my problem. I will pursue your Solution 1 as well to see if it works for me.– TortillaCurtain
Jul 26 at 14:59
Thank you for your very complete example. I am using Webpack, but via create-react-app. I tried your second approach but could not get inject-loader to work. I always received a Cannot find module error despite repeated attempts. I found that if I used refs to pass values, I could change my code to use them rather than
document
calls. I'm not sure if this is the best approach, but it solved my problem. I will pursue your Solution 1 as well to see if it works for me.– TortillaCurtain
Jul 26 at 14:59
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f51484281%2fhow-do-i-deal-with-dom-requests-when-testing-react-code-with-jest-and-enzyme%23new-answer', 'question_page');
}
);
Post as a guest
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Have you tried using
mount
instead of shallow? Generally I feel like enzyme works better when you use the methods they've provided you, like.find()
– tenor528
Jul 23 at 17:55
I did try
mount()
but got the same result.– TortillaCurtain
Jul 23 at 18:00