how to test a react component after data is fetch in componentDidMount? - reactjs

I have a react component that renders conditionally (renders if data is fetched otherwise returns null) and I want to test this with jest & enzyme. The problem that I'm having is I want to test one of the methods in the class but .instance() keeps returning null so it doesn't allow me to test the instance.
my code looks something like this
export default class MyComponent extends React.Component<Props, State> {
componentDidMount() {
this.props.fetchData.then(() =>
this.setState({ loaded: true });
);
}
methodThatIWantToTest() {
//do some stuff here
}
render() {
if (this.loaded) {
// render stuff here
} else {
return null;
}
}
}
and in the test I want to test
describe('myComponent', () => {
it('should do some stuff', () => {
const shallowWrapper = shallow(<MyComponent {...props}/>);
const method = shallowWrapper.instance().methodThatIWantToTest();
....such and such
});
});
but it looks like MyComponent only returns null so shallowWrapper.instance() returns null as well. I tried shallowWrapper.update() and many other things but it seems it doesn't want to render at all.. How do I wait for my component to be updated and then starts expect statement?
has anyone had a similar issue as mine and know how to work around this?

It is render result and not an instance that is null. shallowWrapper.instance() is an instance of component class, it cannot be null for stateful component. As the reference states:
Returns (React 16.x)
ReactComponent: The stateful React component instance.
null: If stateless React component was wrapped.
While shallowWrapper.html() will be initially null indeed.
There is a mistake in original code, it should be this.state.loaded and not this.loaded:
MyComponent extends React.Component {
state = { loaded: false };
componentDidMount() {
this.props.fetchData.then(() => {
this.setState({ loaded: true });
});
}
methodThatIWantToTest() {
//do some stuff here
}
render() {
if (this.state.loaded) {
return <p>hi</p>;
} else {
return null;
}
}
}
componentDidMount and methodThatIWantToTest should be preferably considered different units. They belong to different tests. In case methodThatIWantToTest is called in lifecycle hooks, it may be stubbed in componentDidMount test:
it('should fetch data', async () => {
const props = { fetchData: Promise.resolve('data') };
const shallowWrapper = shallow(<MyComponent {...props}/>);
expect(shallowWrapper.html()).toBe(null);
await props.fetchData;
expect(shallowWrapper.html()).toBe('<p>hi</p>');
});
Then the method can be tested separately. Lifecycle hooks can be disabled to reduce number of moving parts:
it('should do some stuff', () => {
const shallowWrapper = shallow(<MyComponent {...props}/>, {disableLifecycleMethods: true});
const result = shallowWrapper.instance().methodThatIWantToTest();
expect(result).toBe(...);
});

Here is a working example:
myComponent.js
import * as React from 'react';
export default class MyComponent extends React.Component {
constructor(...props) {
super(...props);
this.state = { loaded: false };
}
componentDidMount() {
this.props.fetchData().then(() =>
this.setState({ loaded: true })
);
}
methodThatIWantToTest() {
return 'result';
}
render() {
if (this.state.loaded) {
return <div>loaded</div>;
} else {
return null;
}
}
}
myComponent.test.js
import * as React from 'react';
import { shallow } from 'enzyme';
import MyComponent from './myComponent';
describe('myComponent', () => {
it('should do some stuff', async () => {
const fetchData = jest.fn(() => Promise.resolve());
const props = { fetchData };
const shallowWrapper = shallow(<MyComponent {...props}/>);
expect(shallowWrapper.html()).toBe(null);
expect(shallowWrapper.instance().methodThatIWantToTest()).toBe('result');
// pause the test and let the event loop cycle so the callback
// queued by then() within componentDidMount can run
await Promise.resolve();
expect(shallowWrapper.html()).toBe('<div>loaded</div>');
});
});

Related

Mock setInterval method in react using jest test cases

I want to mock setInterval method and should cover the lines insed the getData method.
Can someone please help me on this.
startInterval() {
setInterval(() => this.getData(), this.state.timeInterval);
}
getData(){
// i want to covet this lines
}
I have tried as bellow
it('should call getTopIntentsSince', () => {
jest.useFakeTimers();
jest.runAllTicks();
})
jest.runAllTicks runs everything in the micro-task queue.
For a setInterval that runs continuously you'll want to use jest.advanceTimersByTime.
Here is a simple example:
code.js
import * as React from 'react';
export class MyComponent extends React.Component {
constructor(...args) {
super(...args);
this.state = { calls: 0, timeInterval: 1000 };
this.startInterval();
}
startInterval() {
setInterval(() => this.getData(), this.state.timeInterval);
}
getData() {
this.setState({ calls: this.state.calls + 1 });
}
render() { return null; }
}
code.test.js
import * as React from 'react';
import { MyComponent } from './code';
import { shallow } from 'enzyme';
test('MyComponent', () => {
jest.useFakeTimers();
const component = shallow(<MyComponent/>);
expect(component.state('calls')).toBe(0); // Success!
jest.advanceTimersByTime(3000);
expect(component.state('calls')).toBe(3); // Success!
})
If you cancel your interval so it doesn't run continuously then you can also use jest.runAllTimers.

React + Axios - Map returns is not a function?

I'm working on my first API with React. I am able to console log my current state after its loaded and the state for that array is set. However, running my component with a prop "FragrancesArray" which is set after loading the data from this.state.fragrances returns in not a function.
Using axios async and await.
No idea why? Can someone help?
Thanks.
My code:
// Core React
import React, { Component } from 'react';
// Axios
import axios from 'axios';
// Constants
import { FRAGRANCES_URL, BLOGS_URL, MAKE_UP_URL } from 'constants/import';
// Components
import Fragrances from 'components/Fragrances/Fragrances';
class App extends Component {
state = {
fragrances: [],
blogs: [],
makeup: []
}
getCoffee() {
return new Promise(resolve => {
setTimeout(() => resolve('☕'), 0); // it takes 1 seconds to make coffee
});
}
async showData() {
try {
// Coffee first
const coffee = await this.getCoffee();
console.log(coffee); // ☕
// Axios API's
const fragranceData = axios(FRAGRANCES_URL);
const blogData = axios(BLOGS_URL);
const makeupData = axios(MAKE_UP_URL);
// await all three promises to come back and destructure the result into their own variables
await Promise.all([fragranceData, blogData, makeupData])
.then((data) => {
this.setState({
fragrances: data[0],
blogs: data[1],
makeup: data[2]
});
const { blogs } = this.state;
console.log(blogs);
})
} catch (e) {
console.error(e); // 💩
}
}
componentDidMount() {
this.showData();
}
render() {
return (
<Fragrances FragranceArray={this.state.fragrances} AppURL={FRAGRANCES_URL} />
)
}
}
export default App;
In react, before you can set/use state, you need to declare it with getInitialState() but with ES6 class model you initialize state in a constructor.
class App extends Component {
constructor(props) {
super(props)
//- Initialize default state values
this.state = {
fragrances: [],
blogs: [],
makeup: []
}
}
//The rest of code stays the same.
render() {
return (
<Fragrances FragranceArray={this.state.fragrances} AppURL={FRAGRANCES_URL} />
)
}
}
More about React state

jest.fn() value must be a mock function or spy received function: [Function getTemplates]

I have a function in module called handlelistOfTemplates which calls actions defined in another file. I want to test when handlelistOfTemplates is called the function in actions file is called with correct parameters.
My container component :
import React from 'react';
import { connect } from 'react-redux';
import {bindActionCreators} from 'redux';
import * as getData from '../../services/DataService';
class Container extends React.Component {
constructor(props){
super(props)
this.props.actions.getTemplates(1);
this.state = {
value: 1
}
}
handlelistOfTemplates = (template, index, value) => {
this.props.selectedTemplate(template);
this.setState({ value });
this.props.actions.getTemplates(template);
};
componentDidMount() {
}
render() {
return(
<ListOfTemplates listOfTemplates={this.props.listOfTemplates} value={this.state.value} onChange={this.handlelistOfTemplates}/>
);
}
}
function mapStateToProps({state}) {
return {
listOfTemplates: state.listOfTemplates
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(getData, dispatch)
};
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(Container);
And my test :
import React from 'react';
import sinon from 'sinon';
import expect from 'expect';
import { shallow } from 'enzyme';
import PropTypes from 'prop-types';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { createMockStore, createMockDispatch } from 'redux-test-utils';
import Container from './Container';
const shallowWithStore = (component, store) => {
const context = {
store,
muiTheme: getMuiTheme(),
};
const childContextTypes = {
muiTheme: React.PropTypes.object.isRequired,
store: PropTypes.object.isRequired
}
return shallow(component, { context, childContextTypes });
};
let store;
const loadComponent = (testState) => {
const props = {
actions: {
getTemplates: () => {return Promise.resolve()}
}
}
store = createMockStore(testState)
return shallowWithStore(<Container {...props}/>, store);
}
const getFakeState = () => {
return {
listOfTemplates: [],
};
}
describe('Container', () => {
let testState, component;
describe("when Appeal template is selected from select template dropdown", () => {
beforeAll(() => {
testState = getFakeState();
component = loadComponent(testState);
});
fit('should update the content in editor', (done) => {
component.dive().find('ListOfTemplates').props().onChange('Appeal', 1, 2);
component.update();
done();
expect(component.dive().state().value).toEqual(2) // error value still is at 1
expect(component.instance().props.actions.getTemplates).toHaveBeenCalled();
});
});
});
When I run the above test I get the following error.
expect(jest.fn())[.not].toHaveBeenCalled()
jest.fn() value must be a mock function or spy.
Received:
function: [Function getTemplates]
Is there something else I should be running to get this to work ?may be my mock is not correct.
even i tried doing this :
jest.spyon(component.instance().props.actions, 'getTemplates'); before expect the error remains same.
Also, when i checking the component's local state has been modified or not. I'm not getting the updated state.
when i call component.dive().find('ListOfTemplates').props().onChange('Appeal', 1, 2);
the component.dive().state().value should become 2 but it is 1 instead.
Could you please help me where i'm doing wrong?
You should pass a Mock function getTemplate to your component, otherwise jest won't be able to check whether it was called or not.
You can do that like this: (notice jest.fn() )
const props = {
actions: {
getTemplates: jest.fn(() => Promise.resolve())
}
}

Enzyme test DOM document using jsdom

I’m trying to test a simple component in React using jsdom dependency. On my component I want to use plain javascript and I’m using document.getElementById() and enzyme is complaining about that. So I decided to try jsdom. However I don’t know if I’m passing the proper html string to it and if I need to use enzyme’s mount instead of shallow.
This is my react component code:
import React, { Component } from 'react';
class LikesAmount extends Component {
constructor() {
super();
this.state = {
totalLikes: null,
counter: 0,
};
}
componentDidMount() {
if (this.state.counter === 0) {
this.setState({
totalLikes: document.getElementById('likesAmount').dataset.likes,
counter: this.state.counter += 1,
});
}
}
render() {
return (
<div id="likesAmount" data-likes="1000">
<h3>Total likes: {this.state.totalLikes}</h3>
</div>
);
}
}
export default LikesAmount;
And this is my react component TEST code:
import React from 'react';
import { shallow } from 'enzyme';
import jsdom from 'jsdom';
import LikesAmount from './likesAmount';
const { JSDOM } = jsdom;
const { document } = (new JSDOM('<!doctype html><html><body><div id="likesAmount" data-likes="1000"></div></body></html>')).window;
global.document = document;
const wait = () => new Promise(resolve => setImmediate(resolve));
describe('likesAmount component', () => {
let wrapper;
beforeEach(async () => {
wrapper = shallow(<LikesAmount />);
await wait();
wrapper.update();
});
it('Return 1000 likes', async () => {
const likes = 1000;
expect(wrapper.find('h3').text()).toBe(`Total likes: ${likes}`);
});
});
The error that I'm getting is "Cannot read property 'find' of undefined". So I suppose it has something to do with jsdom because with a normal shallow passing the component as a parameter reads me the wrapper variable assign to it and the .find method...

get state from redux react component in jest unit test

It seems like you can't test state in react components once you make them connected components. Anyone know why? To illustrate the point, I have a test for a react component that passes without redux and fails as soon as you make it connected.
// MyComponent.jsx
import React from 'react'
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
foo: 'bar'
};
}
render() {
return <div></div>
}
}
export default MyComponent
Here is the passing test:
// MyComponent.test.js
import React from 'react'
import MyComponent from '../src/components/MyComponent'
import { mount } from 'enzyme'
describe('MyComponent', () => {
describe('interactions', () => {
let wrapper
beforeEach(() => {
wrapper = shallow(<MyComponent />)
})
it('foo to equal bar', () => {
expect(wrapper.state().foo).toEqual('bar')
})
})
})
Now I'm introducing Redux and connecting the component:
// MyComponent.jsx
...
export default connect(function (state){
return {
currentUser: state.currentUser
}
})(MyComponent);
And here is the updated test:
// MyComponent.test.js
import React from 'react'
import MyComponent from '../src/components/MyComponent'
import { mount } from 'enzyme'
import configureStore from 'redux-mock-store'
describe('MyComponent', () => {
const state = {}
const mockStore = configureStore()
describe('interactions', () => {
let wrapper
beforeEach(() => {
wrapper = mount(<MyComponent store={ mockStore(state) } />)
})
it('foo to equal bar', () => {
expect(wrapper.state().foo).toEqual('bar')
})
})
})
I would recommend just exporting both the connect() (the default export) and the actual component itself.
This way you can test the component separately outside of the connected version of the component.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
foo: 'bar'
};
}
render() {
return <div></div>
}
}
export MyComponent
export default connect(function (state){
return {
currentUser: state.currentUser
}
})(MyComponent);
and then the test:
import { MyComponent } from '../src/components/MyComponent'
import { mount } from 'enzyme'
describe('MyComponent', () => {
describe('interactions', () => {
let wrapper
beforeEach(() => {
wrapper = shallow(<MyComponent />)
})
it('foo to equal bar', () => {
expect(wrapper.state().foo).toEqual('bar')
})
})
})
That's because connect() generates a wrapper component that manages the store interaction process. In your second snippet, <MyComponent> is now the component generated by connect(), not your original component. You would need to dig another level of nesting deeper in the render hierarchy to check on the contents of the real component's state.

Resources