test restapi inside useeffect in jest - reactjs

I'm new to Jest and I'm writing test case for the below function,
useEffect(() => {
fetch("http://ip-api.com/json")
.then(res => res.json())
.then(data => {
cntCode = `country_code=${data.countryCode}`;
country = `country=${data.country}`;
});
});
I tried few ways to cover using but I'm not able to figure out how to cover this function. Can someone help me in writing the testcase for this please?

Here is the unit test solution:
index.tsx:
import React, { useEffect, useState } from 'react';
export const MyComponent = () => {
const [cntCode, setCntCode] = useState('');
const [country, setCountry] = useState('');
useEffect(() => {
fetch('http://ip-api.com/json')
.then((res) => res.json())
.then((data) => {
setCntCode(data.countryCode);
setCountry(data.country);
});
}, [cntCode, country]);
return (
<div>
country: {country}, cntCode: {cntCode}
</div>
);
};
index.test.ts:
import { MyComponent } from './';
import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
describe('MyComponent', () => {
afterEach(() => {
jest.resetAllMocks();
});
it('should pass', async () => {
const mResponse = jest.fn().mockResolvedValue({ countryCode: 123, country: 'US' });
(global as any).fetch = jest.fn(() => {
return Promise.resolve({ json: mResponse });
});
const wrapper = mount(<MyComponent></MyComponent>);
expect(wrapper.exists()).toBeTruthy();
expect(wrapper.text()).toBe('country: , cntCode: ');
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
});
expect(wrapper.text()).toBe('country: US, cntCode: 123');
expect((global as any).fetch).toBeCalledWith('http://ip-api.com/json');
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/59368499/index.test.tsx (9.629s)
MyComponent
✓ should pass (73ms)
-----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.tsx | 100 | 100 | 100 | 100 | |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 11.64s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59368499

Related

Cannot read property 'then' of undefined - Axios get in React

I want to simulate clicking a button (#reset), button has onClick fetching with axios. I wan to mock this data, but I'm getting an error: "Cannot read property 'then' of undefined"
Test:
import React from "react";
import PersonsList from "./Components/PersonsList/PersonsList";
import * as axios from "axios";
import { shallow } from "enzyme";
const mockData = [
{
gender: "female",
},
];
jest.mock("axios");
describe("axios", () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<PersonsList />);
});
axios.get.mockResolvedValue({ data: mockData });
it("reset", () => {
const resetButton = wrapper.find("#reset");
resetButton.simulate("click");
});
});
button:
<button
className="actions__modify-list"
onClick={() => {
getPeople();
}}
id="reset"
>
Function called on button click:
const getPeople = () => {
const url = "https://randomuser.me/api/?results=10";
axios(url)
.then((res) => {
setPeople(res.data.results);
})
.catch(() => {
setError(true);
});
};
You should mock resolved value for axios(), NOT axios.get() method.
E.g.
PersonsList.jsx:
import axios from 'axios';
import React, { useState } from 'react';
export function PersonsList() {
const [person, setPeople] = useState();
const [error, setError] = useState(false);
const getPeople = () => {
const url = 'https://randomuser.me/api/?results=10';
axios(url)
.then((res) => {
setPeople(res.data.results);
})
.catch(() => {
setError(true);
});
};
return (
<div>
<button
className="actions__modify-list"
onClick={() => {
getPeople();
}}
id="reset"
></button>
</div>
);
}
PersonsList.test.jsx:
import { PersonsList } from './PersonsList';
import axios from 'axios';
import React from 'react';
import { shallow } from 'enzyme';
jest.mock('axios');
const mockData = [{ gender: 'female' }];
describe('68393613', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<PersonsList />);
});
test('should pass', () => {
axios.mockResolvedValue({ data: mockData });
const resetButton = wrapper.find('#reset');
resetButton.simulate('click');
});
});
test result:
PASS examples/68393613/PersonsList.test.jsx (8.311 s)
68393613
✓ should pass (12 ms)
-----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|-------------------
All files | 91.67 | 100 | 80 | 91.67 |
PersonsList.jsx | 91.67 | 100 | 80 | 91.67 | 15
-----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.837 s, estimated 11 s

How to write test case for url push in useEffect react hook?

import { logout } from '../../actions/user.actions';
const Logout = () => {
useEffect(() => {
Router.push('/any');
}, []);
};
Test File
afterEach(() => {
jest.clearAllMocks();
});
afterAll(() => {
jest.resetAllMocks(); // clear all the mocks.
});
it('Should check the route', () => {
const wrapper = mount(<Logout />);
expect(wrapper.find('LoadingMessage')).toBeTruthy();
expect(useDispatch).toBeCalledTimes(1);
const mDispatch = useDispatch();
expect(mDispatch).toBeCalledWith({ type: 'USER_LOGOUT' });
expect(Router.push).toBeCalledWith('/');expect(mDispatch).toBeCalledWith({ type: 'USER_LOGOUT' });
expect(mDispatch).toBeCalledWith('/');
})
})
This is my sample of code that I have written, I want to write a test case for this. So I can test the Router push is happening or not?
Wanting some idea or help.
Thanks in advance.
Below unit test solution mocks useDispatch hook and next/router module without using a mock redux store. Using the mocked redux store and check the state of store after dispatching an action is closer to integration testing.
E.g.
index.jsx:
import React, { useEffect } from 'react';
import Router from 'next/router';
import { useDispatch } from 'react-redux';
import { logout } from './user.actions';
import { LoadingMessage } from './LoadingMessage';
export const Logout = () => {
const dispatch = useDispatch();
const props = {
active: true,
withOverlay: false,
small: false,
};
useEffect(() => {
dispatch(logout());
Router.push('/');
}, []);
return (
<div>
<LoadingMessage {...props}>
<div>Logging out, please wait...</div>
</LoadingMessage>
</div>
);
};
user.actions.ts:
export function logout() {
return {
type: 'LOGOUT',
};
}
LoadingMessage.jsx
export const LoadingMessage = ({ children }) => <div>{children}</div>;
index.test.jsx:
import { Logout } from './';
import { useDispatch } from 'react-redux';
import { mount } from 'enzyme';
import Router from 'next/router';
jest.mock('next/router', () => ({ push: jest.fn() }), { virtual: true });
jest.mock('react-redux', () => {
const originalReactRedux = jest.requireActual('react-redux');
const mDispatch = jest.fn();
const mUseDispatch = jest.fn(() => mDispatch);
return {
...originalReactRedux,
useDispatch: mUseDispatch,
};
});
describe('61928263', () => {
afterEach(() => {
jest.clearAllMocks();
});
afterAll(() => {
jest.resetAllMocks();
});
it('should pass without using mock store', () => {
const wrapper = mount(<Logout></Logout>);
expect(wrapper.find('LoadingMessage')).toBeTruthy();
expect(useDispatch).toBeCalledTimes(1);
const mDispatch = useDispatch(); // get the mocked dispatch function
expect(mDispatch).toBeCalledWith({ type: 'LOGOUT' });
expect(Router.push).toBeCalledWith('/');
});
});
The outcome for the test:
PASS stackoverflow/61928263/index.test.jsx (8.62s)
61928263
✓ should pass without using mock store (37ms)
--------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
LoadingMessage.jsx | 100 | 100 | 100 | 100 |
index.jsx | 100 | 100 | 100 | 100 |
user.actions.ts | 100 | 100 | 100 | 100 |
--------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.601s

Test case for conditional rendering with hooks in react-jest

I am pretty new to the React world. Here's what I have in react-hooks.
export default function MyComponent() {
const [data, setData] = useState(null);
useEffect( () => {
getData().then(setData)
}, []);
return(
data ? <AnotherComponent /> : <LoadingComponent />
);
}
getData() is in another component with 'fetch'.
export function getData() {
return fetch('/api/v2/user').then(response => {
if(response.status === 200) {
return response.json()
} else {
return {};
}
});
}
I am using Jest/Enzyme as a testing framework and want to test the scenario with mock data so I can test LoadingComponent is not present in DOM. This is what I am trying but it seems mock values are not being returned.
const mockValues = {
data: {}
count: 10
result: []
};
jest.mock('../DataService');
const mockService = require('../DataService');
mockService.getData = jest.fn().mockResolvedValue(mockValues);
...
const component = mount(<MyComponent />);
expect(component.html()).toEqual(expect.not.stringContaining('LoadingComponent'));
I see the "mount" works fine but it seems mock values are not being returned and hence LoadingComponent is present.
Here is the unit test solution:
index.tsx:
import React, { useState, useEffect } from 'react';
import { AnotherComponent } from './AnotherComponent';
import { LoadingComponent } from './LoadingComponent';
import { getData } from './dataService';
export default function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
getData().then(setData);
}, []);
return data ? <AnotherComponent /> : <LoadingComponent />;
}
AnotherComponent.jsx:
export const AnotherComponent = () => <div>AnotherComponent</div>;
LoadingComponent.jsx:
export const LoadingComponent = () => <div>LoadingComponent</div>;
dataService.js:
export function getData() {
return fetch('/api/v2/user').then((response) => {
if (response.status === 200) {
return response.json();
} else {
return {};
}
});
}
index.test.jsx:
import React from 'react';
import * as DataService from './dataService';
import { mount } from 'enzyme';
import MyComponent from './';
import { act } from 'react-dom/test-utils';
jest.mock('./dataService');
const whenStable = async () => {
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
});
};
describe('60913717', () => {
it('should pass', async () => {
const mockValues = {
data: {},
count: 10,
result: [],
};
DataService.getData = jest.fn().mockResolvedValueOnce(mockValues);
const wrapper = mount(<MyComponent></MyComponent>);
expect(wrapper.find('LoadingComponent')).toBeTruthy();
await whenStable();
expect(wrapper.find('AnotherComponent')).toBeTruthy();
});
});
unit test results with coverage report:
PASS stackoverflow/60913717/index.test.jsx (28.041s)
60913717
✓ should pass (48ms)
----------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------------|---------|----------|---------|---------|-------------------
All files | 80 | 50 | 66.67 | 75 |
AnotherComponent.jsx | 100 | 100 | 100 | 100 |
LoadingComponent.jsx | 100 | 100 | 100 | 100 |
dataService.js | 20 | 0 | 0 | 20 | 2-4,6
index.jsx | 100 | 100 | 100 | 100 |
----------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 29.721s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60913717

How to test basic example of async function in useEffect

I have a simple component, it fetches async list of posts.
export const Posts = () => {
const [list, dispatch] = useReducer(listReducer, []);
useEffect(() => {
fetchList(dispatch);
}, []);
return (
<ul>
{list.map((el) => (
<li key={el.id}>{el.title}</li>
))}
</ul>
);
};
In other file i keep the logic:
export const fetchList = async (dispatch) => {
try {
const result = await api.get('/list/') /* AXIOS */
dispatch({ type: LIST_SUCCES, payload: result.data.list })
} catch (error) {
dispatch({ type: LIST_FAILURE })
}
}
export const listReducer = (state, action) => {
switch (action.type) {
case LIST_SUCCES:
return action.payload
case LIST_FAILURE:
return []
default:
throw new Error()
}
}
I've tried multiple libraries but i'm just unable to write a test. How can i write Posts.test.js to check if post are fetched and displayed, i'm triggering async fetchList after first mount of the component (so it is not componentDidMount), and after data are fetched i dispatch action from that async function and update the list.
Here is the unit test solution:
index.tsx:
import React, { useReducer, useEffect } from 'react';
import { listReducer, fetchList } from './reducer';
export const Posts = () => {
const [list, dispatch] = useReducer(listReducer, []);
useEffect(() => {
fetchList(dispatch);
}, []);
return (
<ul>
{list.map((el) => (
<li key={el.id}>{el.title}</li>
))}
</ul>
);
};
reducer.ts:
import axios from 'axios';
const LIST_SUCCES = 'LIST_SUCCES';
const LIST_FAILURE = 'LIST_FAILURE';
export const fetchList = async (dispatch) => {
try {
const result = await axios.get('/list/'); /* AXIOS */
dispatch({ type: LIST_SUCCES, payload: result.data.list });
} catch (error) {
dispatch({ type: LIST_FAILURE });
}
};
export const listReducer = (state, action) => {
switch (action.type) {
case LIST_SUCCES:
return action.payload;
case LIST_FAILURE:
return [];
default:
throw new Error();
}
};
index.spec.tsx:
import React from 'react';
import { Posts } from './';
import { mount } from 'enzyme';
import axios from 'axios';
import { act } from 'react-dom/test-utils';
describe('Posts', () => {
afterAll(() => {
jest.restoreAllMocks();
});
it('should render list correctly', async () => {
const mResponse = { data: { list: [{ id: 1, title: 'jest' }] } };
jest.spyOn(axios, 'get').mockResolvedValueOnce(mResponse);
const wrapper = mount(<Posts></Posts>);
expect(wrapper.find('ul').children()).toHaveLength(0);
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
});
wrapper.update();
expect(wrapper.find('ul').children()).toHaveLength(1);
expect(wrapper).toMatchInlineSnapshot(`
<Component>
<ul>
<li
key="1"
>
jest
</li>
</ul>
</Component>
`);
});
it('should render empty list when request list data failed', async () => {
const mError = new Error('Internal server error');
jest.spyOn(axios, 'get').mockRejectedValueOnce(mError);
const wrapper = mount(<Posts></Posts>);
expect(wrapper.find('ul').children()).toHaveLength(0);
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
});
wrapper.update();
expect(wrapper.find('ul').children()).toHaveLength(0);
expect(wrapper).toMatchInlineSnapshot(`
<Component>
<ul />
</Component>
`);
});
});
Unit test result with coverage report:
PASS src/stackoverflow/59197574/index.spec.tsx (12.494s)
Posts
✓ should render list correctly (105ms)
✓ should render empty list when request list data failed (37ms)
› 1 snapshot written.
------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------|----------|----------|----------|----------|-------------------|
All files | 95.83 | 66.67 | 100 | 95 | |
index.tsx | 100 | 100 | 100 | 100 | |
reducer.ts | 92.86 | 66.67 | 100 | 91.67 | 21 |
------------|----------|----------|----------|----------|-------------------|
Snapshot Summary
› 1 snapshot written from 1 test suite.
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 1 written, 1 passed, 2 total
Time: 14.409s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59197574

Test the dispatch function with jest

How can I test the return dispatch from mapDispatchToProps?
export const mapDispatchToProps = (dispatch) => {
return {
sidebarSettings: () => {
dispatch(showSettings)
},
setFilterText: (value) => {
dispatch(setFilterText(value))
},
setExportCount: () => {
dispatch(setExportCount())
}
}
}
Have this but not working...
describe('mapDispatchToProps', () => {
test('should retrun a dispatch of sidebar settings, filter text and of export count', () => {
const wrapper = shallow(<mapDispatchToProps />)
expect(wrapper.find('dispatch')).toHaveLength(3)
})
})
Here is the solution, mapDispatchToProps function just a javascript function. You don't need to test it using Shallow render with enzyme.
const ACTION_TYPES = {
SHOW_SETTINGS: 'SHOW_SETTINGS',
SET_FILTER_TEXT: 'SET_FILTER_TEXT',
SET_EXPORT_COUNT: 'SET_EXPORT_COUNT'
};
export const showSettings = { type: ACTION_TYPES.SHOW_SETTINGS };
export const setFilterText = value => ({ type: ACTION_TYPES.SET_FILTER_TEXT, payload: { value } });
export const setExportCount = () => ({ type: ACTION_TYPES.SET_EXPORT_COUNT });
export const mapDispatchToProps = dispatch => {
return {
sidebarSettings: () => {
dispatch(showSettings);
},
setFilterText: value => {
dispatch(setFilterText(value));
},
setExportCount: () => {
dispatch(setExportCount());
}
};
};
Unit test:
import { mapDispatchToProps, showSettings, setFilterText, setExportCount } from './';
const dispatch = jest.fn();
describe('mapDispatchToProps', () => {
it('t1', () => {
const actualValue = mapDispatchToProps(dispatch);
expect(Object.keys(actualValue)).toEqual(['sidebarSettings', 'setFilterText', 'setExportCount']);
actualValue.sidebarSettings();
expect(dispatch).toBeCalledWith(showSettings);
actualValue.setFilterText('name');
expect(dispatch).toBeCalledWith(setFilterText('name'));
actualValue.setExportCount();
expect(dispatch).toBeCalledWith(setExportCount());
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/57802233/index.spec.ts
mapDispatchToProps
✓ t1 (11ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.201s
mapDispatchToProps isn't a React component so you can't render it. If you really want to test that it returns the defined functions, which doesn't make much sense imo, you can just check if the object has the defined props:
describe('mapDispatchToProps', () => {
test('should retrun a dispatch of sidebar settings, filter text and of export count', () => {
expect(Object.keys(mapDispatchToProps())).toHaveLength(3)
})
})
mapDispatchToProps is not React component you don't need enzyme to test this.
one of way testing is you can pass mocked dispatch and call all the function and check if mocked dispatch is called that many times.
describe('mapDispatchToProps', () => {
const dispatch = jest.fn();
const props = mapDispatchToProps(dispatch);
props.setExportCount();
props.setFilterText();
props.sidebarSettings();
expect(dispatch).toHaveBeenCalledTimes(3);
})

Resources