I am trying to perform some unit testing on my existing react application with Jest and Enzyme. I am totally new to this stuff and accurately I do not know how to approach such test scenarios. I know that to test API request calls I have to perform some "mocking", but how should I write the test for that?. What will be the steps that needs to be followed?
Following is the code snippet I am looking to test.
Home.js
import React,{Component} from 'react'
import axios from 'axios';
import {Link} from 'react-router-dom';
import FacilityModal from '../Table/FacilityModal';
class Home extends Component {
state = {
cities:[],
name:''
}
componentDidMount() {
axios.get('/cities').then(res => {
this.setState({cities:res.data})
console.log("Oza" + JSON.stringify(res))
});
console.log(this.state.cities)
}
render() {
let postList = this.state.cities.map(city => {
return(
<div key = {city.id}>
<p>
<Link to = {'/'+city.id}>{city.name}</Link>
</p>
</div>
)
})
return(
<div className = 'align'>All Facilities (NCAL)
<div className="hr-sect">OR</div>
<div className = 'Modal'>
{postList}
</div>
<FacilityModal cityname = {this.props} />
</div>
)
}
}
import React from 'react';
import axios from 'axios';
export default class ArticleList extends React.Component {
constructor(props) {
super(props);
this.state = {
articles: []
}
}
componentDidMount() {
return axios.get('GET_ARTICLES_URL').then(response => {
this.setState({
articles: response.data
});
});
}
render() {
return (
<ul>
{this.state.articles.map(a => <li><a href={a.url}>{a.title}</a></li>)}
</ul>
)
}
}
// ---------
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';
jest.mock('axios', () => {
const exampleArticles = [
{ title: 'test article', url: 'test url' }
];
return {
get: jest.fn(() => Promise.resolve(exampleArticles)),
};
});
const axios = require('axios');
it('fetch articles on #componentDidMount', () => {
const app = shallow(<App />);
app
.instance()
.componentDidMount()
.then(() => {
expect(axios.get).toHaveBeenCalled();
expect(axios.get).toHaveBeenCalledWith('articles_url');
expect(app.state()).toHaveProperty('articles', [
{ title: 'test article', url: 'test url' }
]);
done();
});
});
1) Extract the API call in another method that returns the promise(for eg: fetchCities()) for ease of testing.
2) Mock the axios node module with Jest. Refer docs: https://jestjs.io/docs/en/mock-functions#mocking-modules
3) Use Enzyme to get a reference to your component: https://airbnb.io/enzyme/docs/api/ShallowWrapper/shallow.html
Once that's in place, you can verify if the state is set correctly. For eg:
test('should fetch users', () => {
const wrapper = shallow(<Home/>);
const resp = {data: [{cities: ['NY']}]};
axios.get.mockResolvedValue(resp);
wrapper.instance().fetchCities().then(resp => {
expect(wrapper.state('cities')).toEqual(resp.data.cities);
});
});
How do i improve this answer? It is not what i am expecting as response which is name of the cities.
axios.js (seperate function for promise)
'use strict';
module.exports = {
get: () => {
return Promise.resolve({
data: [
{
id: 0,
name: 'Santa Clara'
},
{
id: 1,
name: 'Fremont'
}
]
});
}
};
Home.test.js (actual test file)
import React from 'react';
import { shallow,configure } from 'enzyme';
import Home from './Home';
import axios from 'axios';
import Adapter from 'enzyme-adapter-react-16';
configure({adapter:new Adapter()});
jest.mock('axios');
describe('Home component', () => {
describe('when rendered', () => {
it('should fetch a list of cities', () => {
const getSpy = jest.spyOn(axios, 'get');
const cityInstance = shallow(
<Home/>
);
expect(getSpy).toBeCalled();
});
});
});
Related
I am fairly new to testing React applications with Jest and Enzyme. I am trying to test some connected components, but don't understand how to mock data for those. I have an API call and use props. Here are my files:
News Page
import React, { Component } from 'react';
import './news.style.css';
import NewsList from '../../components/news-list/news-list.component';
import { SearchBar } from '../../components/search-bar/search-bar.component';
import Header from '../../components/header/header.component';
import { NewsFooter } from '../../components/news-footer/news-footer.component';
class News extends Component {
constructor() {
super();
this.state = {
news: [],
searchField: '',
topics: ''
};
}
componentDidMount() {
fetch('https://sheltered-earth-94987.herokuapp.com/news')
.then(response => response.json())
.then(news => this.setState({ news: news}));
}
render() {
const { news, searchField, topics } = this.state;
const filteredNewsname = news
.filter(news => news.news_topic.toLowerCase().includes(topics.toLowerCase()))
.filter(news => news.news_headline.toLowerCase().includes(searchField.toLowerCase()));
return (
<div>
<Header/>
<h1 className="pageheadline">News</h1>
<SearchBar
placeholder='Search News'
handleChange= {e => this.setState({ searchField: e.target.value})}
/>
<div className="newslist">
<NewsList news={filteredNewsname}>
</NewsList>
</div>
<div className="newsfooter">
<NewsFooter
handleClick= {e => this.setState({ topics: e.target.id})}
/>
</div>
</div>
);
}
}
export default News;
NewsList Component
import React from 'react';
import './news-list.style.css';
import { NewsCard } from '../news-card/news-card.component';
import { Link } from 'react-router-dom';
const NewsList = props => {
return <div className='news-list'>
{
props.news.map(newsentry => <Link to={`/news/${newsentry.news_id}`}>
<NewsCard key={newsentry.news_id} newsentry={newsentry}/></Link>)
}
</div>;
};
export default NewsList;
NewsCard Component
import React from 'react';
import './news-card.style.css';
const NewsCard = props => (
<div className='news-card-container' data-test="news-card-container">
<img className="newsimg" alt="Newsimage" src={ props.newsentry.news_header_image}></img>
<div className="newsinfo">
<h4 className="newstitle"> { props.newsentry.news_headline } </h4>
<p className="teaser">{props.newsentry.news_teaser}</p>
<p className="author">By {props.newsentry.news_author} </p>
</div>
<p className="newstopic">#{props.newsentry.news_topic}</p>
</div>
)
export default NewsCard;
How can I test the NewsList and the NewsCard Components with mocked data?
This is how I started:
Testfile
import { shallow } from 'enzyme';
import React from 'react';
import NewsCard from './news-card.component';
import { findByTestAttr } from '../../../utils/index';
const setUp = (props={}) => {
const component = shallow(<NewsCard {... props}/>);
return component;
}
describe('NewsCard Component', () => {
describe('Have props', () => {
let wrapper;
beforeEach(() => {
const props = {
news: [],
};
wrapper = setUp(props);
});
it('Should render without errors', async () => {
const component = findByTestAttr(wrapper, 'news-card-container');
expect(component.length).toBe(1);
})
});
})
File with findByTestAttr function
export const findByTestAttr = (component, attr) => {
const wrapper = component.find(`[data-test='${attr}']`);
return wrapper;
}
For this right now I get an error, saying:
TypeError: Cannot read properties of undefined (reading 'news_header_image')
Before rendering components in test block; you can provide a new, mockup variable to your component.
Example:
<NewsCard key={..} newsentry={mockupNewsentry}/> with mockupNewsentry being your mockup data variable.
Longer example:
test("renders singleitem with data", async () => {
const mockupData = {
name: "Homer Simpson",
job: "Nuclear Safety Inspector",
id: "14",
};
render(
<SingleItem data={mockupData} />
);
const element = await screen.findByText(/Homer Simpson/i);
expect(element).toBeInTheDocument();
});
Check out this package. It will mock the network layer. Everyone is using this one for integration testing of components.
https://mswjs.io
I am using jest and enzyme for Unit Testing of a React App created using create-react-app. I have an axios call and I call other functions like myfunction.onSuccess() and myfunction.onError() from another component on success and error of the axios call respectively. I have put an assert to expect(myfunction.onSuccess).toHaveBeenCalledTimes(1); This works fine for mockResolvedValue but not for mockRejectedValue. I see that async-await is not respected for mockRejectedValue.
I have created a sample code and used window.alert as the function to be called in both then and catch block to keep it simple here and expecting this function to be called once as an assert. Why is there a difference and how to tackle this?
Async.js
import React from 'react';
import axios from 'axios';
class Async extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: []
};
}
getAllPosts() {
axios.get(`https://jsonplaceholder.typicode.com/posts`)
.then(response => {
if (response.data) {
this.setState({ posts: response.data });
window.alert("Data fetch Success");
}
}).catch(error => {
window.alert(error.message);
});
}
render() {
return (
<div>
<button onClick={() => { this.getAllPosts() }}>Fetch Posts</button>
<ul>
{this.state.posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
}
export default Async;
Async.test.js
import React from 'react';
import Async from './Async';
import { shallow } from 'enzyme';
import axios from "axios";
jest.mock("axios");
describe('Async Component', () => {
const wrapper = shallow(<Async />);
const layout = wrapper.instance();
it('to notify success when posts', async () => {
window.alert = jest.fn();
const response = { data: [{ id: '1', title: 'something' }, { id: '2', title: 'something else' }] };
axios.get.mockResolvedValue(response);
await layout.getAllPosts();
expect(layout.state.posts).not.toBe([])
expect(window.alert).toHaveBeenCalledTimes(1);
});
it('to notify failure when error is encountered', async () => {
window.alert = jest.fn();
const error = { message: '500 Internal Server Error' };
axios.get.mockRejectedValue(error);
await layout.getAllPosts();
expect(window.alert).toHaveBeenCalledTimes(1); // why doesn't this work?
});
});
I'm trying to test react-redux connected app which is having aync action to fetch data from API. But the test fails for some reason. what wrong i'm doing here?
AssertionError: expected { length: 0 } to have a length of 1 but got 0
posts.js(Redux Action)
import instance from "./../config/axiosconfig";
export const postList = () => {
return dispatch => {
return instance.get("/posts")
.then(res => {
const posts = res.data;
return dispatch({
type: "POST_LIST", posts
});
});
};
};
posts.js(React Component)
import React, { Component } from "react";
import { connect } from "react-redux";
import {postList} from "../actions/posts";
import PropTypes from "prop-types";
class Post extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
if (this.props.posts === undefined || this.props.posts.length === 0) {
const {dispatch} = this.props;
dispatch(postList());
}
}
render() {
let postLists = "";
if (this.props.posts) {
postLists = this.props.posts.map((list, i) => (
<li key = {i}>
{list.title}
</li>
));
}
return (
<div>
<h2>About</h2>
<p>Services</p>
<ol className="post-list">
{postLists}
</ol>
</div>
);
}
}
Post.propTypes = {
posts: PropTypes.array,
dispatch: PropTypes.func
};
const mapDispatchToProps = (dispatch) => ({ dispatch });
const mapStateToProps = (state) => {
return { posts: state.PostReducer ? state.PostReducer.posts : [] };
};
export default connect(mapStateToProps, mapDispatchToProps)(Post);
Post.test.js(Test for component)
import React from "react";
import Post from "client/components/post";
import { Provider } from 'react-redux';
import PostReducer from "client/reducers/posts";
import {mount, render, shallow} from 'enzyme'
import instance from "client/config/axiosconfig";
import { expect } from "chai";
import moxios from "moxios";
import { createStore, applyMiddleware, combineReducers } from 'redux';
import configureStore from "redux-mock-store";
import thunkMiddleware from "redux-thunk";
let store;
let wrapper;
describe("Post Component", () => {
beforeEach(() => {
moxios.install(instance);
});
afterEach(() => {
moxios.uninstall(instance);
});
it("has expected posts lists listed", async () => {
store = setupStore();
const payload = [{
body: "TEST",
id: 1,
title: "Test Title"
}];
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: payload
});
});
wrapper = mount(<Provider store={store}><Post/></Provider>);
expect(wrapper.find('.post-list li')).to.have.length(1);
});
function setupStore() {
return createStore(
combineReducers({ PostReducer }),
applyMiddleware(thunkMiddleware)
);
}
});
The request which you have stubbed may still be pending by the time make an assertion.
Try the following:
it("has expected posts lists listed", async () => {
store = setupStore();
const payload = [{
body: "TEST",
id: 1,
title: "Test Title"
}];
const promise = moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: payload
});
});
const wrapper = mount(<Provider store={store}><Post/></Provider>);
await promise;
wrapper.update(); // May not be necessary
expect(wrapper.find('.post-list li')).to.have.length(1);
});
I'm having trouble testing a react component that uses localstorage to save a JWT token and retrieves it for api calls and routing based on authentication.
The component itself works fine, but when i test, i'm getting this error in all the three tests
TypeError: localStorage.getItem is not a function
Here is the code i have written
home.test.js
import React from 'react';
import { shallow, mount } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';
import Home from '../containers/Home.jsx';
describe('<Home />', () => {
beforeAll(() => {
global.localStorage = {
i2x_token: 'someToken',
};
});
it('renders without exploding', () => {
shallow(<Home />);
});
it('renders children when passed in', () => {
const wrapper = shallow(
<Home>
<div className='unique' />
</Home>,
);
expect(wrapper.contains(<div className='unique' />)).to.equal(true);
});
it('calls componentWillMount', () => {
sinon.spy(Home.prototype, 'componentWillMount');
const wrapper = mount(<Home />);
expect(Home.prototype.componentWillMount).to.have.property('callCount', 1);
Home.prototype.componentWillMount.restore();
});
});
home.jsx
import React, { Component } from 'react';
import { browserHistory } from 'react-router';
import Header from '../components/Header.jsx';
import Content from '../components/Content.jsx';
import { API_CONTENT } from '../utils/constants';
import request from '../utils/request';
class Home extends Component {
constructor(props) {
super(props);
this.state = {
content: null,
};
this.logout = this.logout.bind(this);
}
componentWillMount() {
const token = localStorage.getItem('i2x_token');
const requestURL = API_CONTENT;
const requestObj = {
method: 'GET',
headers: new Headers({
Authorization: `JWT ${token}`,
}),
};
request(requestURL, requestObj).then((reply) => {
if (reply.results.length > 0) {
this.setState({ content: reply.results });
} else {
console.log('no reply from API');
}
});
}
logout() {
localStorage.removeItem('i2x_token');
browserHistory.push('/');
}
render() {
const data = this.state.content;
if (data !== null) {
return (
<div className='container'>
<Header logout={ this.logout } />
<Content data={ this.state.content } />
</div>
);
}
return (
<div className='container'>
<Header logout={ this.logout } />
</div>
);
}
}
export default Home;
localStorage is part of your Browser which is not available in a unit test, you need to mock it. You can mock the necessary methods in the localStorage object:
home.test.js
beforeAll(() => {
global.localStorage = {
i2x_token: 'someToken',
getItem: function () {
return 'someToken'
}
};
});
....
I recently had the same issue and I resolved it by using the following: "mock-local-storage": "^1.0.4". The package can be found here.
This module mocks the localStorage and the sessionStorage for you, which was hustle free for me. The plug in allows you to add middleware to the store such as redux-thunk and redux-sagas.
N.B. I am using Mocha to run my tests.
For other frameworks you can use the following configuration
global.window = {}
import localStorage from 'mock-local-storage'
window.localStorage = global.localStorage
Here is a simplified version of my component:
import React from 'react';
import { observable, action } from 'mobx';
import { observer } from 'mobx-react';
import { fromPromise } from 'mobx-utils';
#observer
export class MyComponent extends React.Component {
#action componentDidMount() {
const { store, params } = this.props;
this.warehouse = store.findById(params.id);
}
#observable warehouse = fromPromise(Promise.resolve());
render() {
return this.warehouse.case({
fulfilled: (value) => (
<div>
fulfilled
</div>
),
rejected: (error) => (
<div>
rejected
</div>
),
pending: () => (
<div>
pending
</div>
)
});
}
}
And here is my test (using jest and enzyme):
import React from 'react';
import { mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import { observable, when } from 'mobx';
import { fromPromise } from 'mobx-utils';
import { MyComponent } from './MyComponent';
describe('<MyComponent>', () => {
it('should render correctly for state "fulfilled"', (done) => {
const mockStore = observable({
findById: jest.fn(() => fromPromise(Promise.resolve({ id: 'id' })))
});
const wrapper = mount(<MyComponent store={mockStore} params={{ id: '1' }} />);
const wh = wrapper.instance().warehouse;
when(
() => wh.state === 'fulfilled',
() => {
expect(wrapper.text()).toBe('fulfilled');
done();
}
);
});
});
The problem is that the handler for when in test runs before render method, so I don't have access to rendered markup there.
My question is how to run my except codes after rendering the fulfilled state.
Also I don't want to hack my component. Here I am using wrapper.instance().warehouse which I don't like very much.
Generally, the question would be how to test components with observable states in them?
I ended up with this solution:
import React from 'react';
import { mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import { observable, when } from 'mobx';
import { fromPromise } from 'mobx-utils';
import { MyComponent } from './MyComponent';
describe('<MyComponent>', () => {
it('should render correctly for state "fulfilled"', (done) => {
const mockStore = observable({
findById: jest.fn(() => fromPromise(Promise.resolve({ id: 'id' })))
});
const wrapper = mount(<MyComponent store={mockStore} params={{ id: '1' }} />);
const wh = wrapper.instance().warehouse;
when(
() => wh.state === 'fulfilled',
() => {
process.nextTick(() => {
expect(wrapper.text()).toBe('fulfilled');
done();
});
}
);
});
});
Also there is a related question in mobx-react project issues.