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?
});
});
Related
Below is my App.js code for your reference
import React from "react";
import "./App.css";
import axios from "axios";
function App() {
const fetchTheComments = async () => {
let commentsFetched = await axios.get(
`https://jsonplaceholder.typicode.com/comments/1`
);
return commentsFetched;
};
return (
<div className="App">
<h1>Testing Jest-Enzyme</h1>
<button
id="fetch-comments"
onClick={() => {
fetchTheComments();
}}
>
Fetch
</button>
<p>
{JSON.stringify(fetchTheComments())
? JSON.stringify(fetchTheComments())
: ""}
</p>
</div>
);
}
export default App;
Below is my App.test.js code for your reference
import App from "./App";
import { mount } from "enzyme";
import mockAxiosApi from "../src/__mocks__/mockAxiosApi";
describe("Before testing", () => {
let wrapper;
beforeAll(() => {
wrapper = mount(<App />);
});
test("render the correct title", () => {
expect(wrapper.find("h1").text()).toBe("Testing Jest-Enzyme");
});
test("button click", () => {
wrapper.find("#fetch-comments").simulate("click");
expect(wrapper.find("comments")).not.toBe("");
});
test("should fetch comments", async () => {
wrapper.find("#fetch-comments").simulate("click");
mockAxiosApi.get.mockImplementationOnce(() =>
Promise.resolve({
data: {},
})
);
console.log(wrapper.debug());
let response = await wrapper.instance().fetchTheComments();
console.log(response);
});
});
I am not sure why i am getting the error, i have one lambda function inside the component which i am testing but whenever i run a test getting an error stating fetchTheComments function is null. I have pasted my App.js and App.test.js here for your reference. Can someone help me in this issue ?
I'm trying to provide a mock request for this class and then expect that history.push is called with some path.
Start.js
import React from 'react'
import { useHistory } from 'react-router-dom';
import axios from 'axios';
import { ReactComponent as Arrow } from '../../arrow.svg';
export default function Start() {
let history = useHistory();
const doInitializeApp = () => {
axios.get('http://localhost:8080/api/v1/asap/start')
.then(res => {
if (res.data == true) {
history.push('/login')
} else {
alert('something went wrong. Could not start the application')
}
}).catch(err => {
alert('something went wrong. Could not contact the server!')
});
}
return (
<div>
<div className="container">
<div className="content">
<div id="box">
<h1>Welcome</h1>
<Arrow id="next" onClick={doInitializeApp} />
</div>
</div>
</div>
</div>
);
}
And this is my approach for the test
Start.test.js
import React from 'react';
import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Start from '../components/startscreen/Start';
import { ReactComponent as Arrow } from '../arrow.svg';
import axios from "axios";
Enzyme.configure({ adapter: new Adapter() });
describe('Start', () => {
it('test axios get reroute the application to path /login', () => {
const mProps = { history: { push: jest.fn() } };
const wrapper = shallow(<Start {...mProps} />);
const arrow = wrapper.find(Arrow);
const axiosSpy = jest.spyOn(axios, 'get');
//mock axios
jest.mock("axios");
//mock axios response
axios.get.mockResolvedValue({ data: true });
//simulate onclick
arrow.simulate('click');
expect(axiosSpy).toHaveBeenCalled(); --> this pass
expect(mProps.history.push).toBeCalledWith('/login'); --> this doesn't pass
})
});
However, the test did not pass because the actual axios.get(url) doesn't take the response which I mocked and it always come to the .catch(err => ... "Could not contact the server!")
What did I do wrong in here ? Because that the code didn't come to the if (res.data===true) so that I also couldn't test whether the history.push is actually called or not.
Your mocking code is fine. The code in the catch block is getting executed since useHistory() returns undefined (You can confirm this by console.logging the error inside the catch block).
One way to fix it would be to mock useHistory and pass a mock function for history.push. You can then spy on useHistory() to confirm the history.push got called with /login.
import { useHistory } from 'react-router-dom'
// other import statements omitted for brevity
jest.mock('axios')
jest.mock('react-router-dom', () => {
const fakeHistory = {
push: jest.fn()
}
return {
...jest.requireActual('react-router-dom'),
useHistory: () => fakeHistory
}
})
const flushPromises = () => new Promise(setImmediate)
describe('Start component', () => {
test('redirects to /login', async () => {
const pushSpy = jest.spyOn(useHistory(), 'push')
axios.get.mockResolvedValue({ data: true })
const wrapper = shallow(<App />)
const button = wrapper.find(Arrow)
button.simulate('click')
await flushPromises()
expect(pushSpy).toBeCalledWith('/login')
})
})
I'm using setImmediate to wait for the async action to complete as suggested here.
I am using #woocommerce/woocommerce-rest-api package for my api. I am using NextJS and React Redux. Here is my woocommerce configuration:
import WooCommerceRestApi from '#woocommerce/woocommerce-rest-api';
export const wooApi = new WooCommerceRestApi({
url: 'MY_API_URL',
consumerKey: 'MY_CONSUMER_KEY',
consumerSecret: 'MY_CONSUMER_SECRET',
version: 'wc/v3',
queryStringAuth: true,
});
I dispatch an action right away when the component mounts.
Here's how I use the API in my action:
export const fetchMainProductCategories = () => {
return async (dispatch: Dispatch) => {
try {
const response = await wooApi.get(`products/categories?hide_empty=true&parent=0`);
dispatch<FetchMainProductCategories>({
type: CategoryTypes.fetchMainProductCategories,
payload: response.data,
});
} catch (error) {
console.log(error);
}
};
};
Here's my initial test statements so far but I doesn't work:
import React from 'react';
import '../../__mocks__/matchMedia';
import MockCategories from '../../__mocks__/mockCategories';
import { render, cleanup, logDOM } from '#testing-library/react';
import Index from '../../pages/index';
import Root from '../../Root';
import { wooApi } from '../../config';
jest.mock('../../config');
describe('Homepage', () => {
beforeEach(() => {
render(
<Root>
<Index />
</Root>
);
});
afterEach(cleanup);
it('loads Product Categories', async () => {
wooApi.get.mockResolvedValueOnce({
data: MockCategories,
});
logDOM();
// const list = await waitFor(() => screen.getByTestId('category-list'));
});
});
You need to register the get method of the wooApi as a mock, while preserving the other features of the api. ie:
import { wooApi } from '../../config'
import { fetchMainProductCategories } from '../where-it-is-defined'
// mark get method as jest mock
jest.mock('../../config', () => ({
...jest.requireActual('../../config'), // to avoid overriding other methods/features
get: jest.fn(), // override get method of the api
}))
describe('Homepage', () => {
beforeEach(()=>{
wooApi.get.mockResolvedValue({
status: 200,
data: { categories: ['a', 'b'] },
})
test('loads ...', async () => {
const dispatch = jest.fn()
await fetchMainProductCategories()(dispatch)
expect(dispatch).toHaveBeenCalledWith(
{ type: '...',
payload: { categories: ['a', 'b'] }
}
)
})
})
Ref:
Bypassing Module Mocks in Jest
Edited: My bad, by doing jest.spyOn(config.wooApi, 'get') we are only mocking "get" method of a single instance. The following edited code should work
You can also use jest.spyOn to only mock the get method like below
import * as config from '../../config'
jest.spyOn(WooCommerceRestApi.prototype, 'get')
WooCommerceRestApi.prototype.get.mockResolvedValue('...')
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();
});
});
});
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