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

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

Related

Test if function have been called - React,

I am trying to test a simple component.
In my component, I am calling the fetchPdf function with useEffect.
Inside fetchPdf function I am making axios request and if the call was successful I am setting the response (which is the pdf) in the state.
My component:
import React, { useState, useEffect } from "react";
import axios from 'axios';
export default function App() {
const [pdf, setPdf] = useState();
const fetchPdf = () => {
// Here I am make an API call - on success I am
// set the pdf from the response into state
// axios.get('url/endpoint')
// .then((res) => {
// if (res.status === 200) {
// setPdf(res.data);
// }
// }).catch((e) => console.log(e.message));
setPdf("Mocked PDF");
};
useEffect(() => {
fetchPdf();
}, []);
return (
<div className="App">
<h1>My pdf from state: {pdf}</h1>
</div>
);
}
My test:
import React from "react";
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import App from "./App";
Enzyme.configure({ adapter: new Adapter() });
describe("<App />", () => {
let wrapper;
const setState = jest.fn();
const useStateSpy = jest.spyOn(React, "useState");
useStateSpy.mockImplementation((init) => [init, setState]);
beforeEach(() => {
wrapper = Enzyme.shallow(<App />);
});
afterEach(() => {
jest.clearAllMocks();
});
describe("calling designPDF", () => {
it("set designPDF into the state", () => {
// Not sure how can I test the fetchPDF function as been called
});
});
});
Codesendbox example
useEffect does not support shallow rendering yet. So you should use Full Rendering API (mount(...)).
Besides, you should mock axios.get method and its resolved value. Call the whenStable function to ensure that the promise returned by the axios.get method is resolved or rejected
E.g.
App.jsx:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function App() {
const [pdf, setPdf] = useState();
const fetchPdf = () => {
axios
.get('url/endpoint')
.then((res) => {
if (res.status === 200) {
setPdf(res.data);
}
})
.catch((e) => console.log(e.message));
};
useEffect(() => {
fetchPdf();
}, []);
return (
<div className="App">
<h1>My pdf from state: {pdf}</h1>
</div>
);
}
App.test.jsx:
import App from './App';
import axios from 'axios';
import { mount } from 'enzyme';
import React from 'react';
import { act } from 'react-dom/test-utils';
const whenStable = async () => {
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
});
};
describe('65310275', () => {
it('should get pdf', async () => {
const getSpy = jest.spyOn(axios, 'get').mockResolvedValueOnce({ data: 'Mocked PDF', status: 200 });
const wrapper = mount(<App></App>);
await whenStable();
expect(wrapper.find('h1').text()).toEqual('My pdf from state: Mocked PDF');
expect(getSpy).toBeCalledWith('url/endpoint');
getSpy.mockRestore();
});
it('should handle error', async () => {
const mErr = new Error('timeout');
const getSpy = jest.spyOn(axios, 'get').mockRejectedValueOnce(mErr);
const logSpy = jest.spyOn(console, 'log');
const wrapper = mount(<App></App>);
await whenStable();
expect(wrapper.find('h1').text()).toEqual('My pdf from state: ');
expect(getSpy).toBeCalledWith('url/endpoint');
expect(logSpy).toBeCalledWith('timeout');
getSpy.mockRestore();
});
});
unit test result:
PASS examples/65310275/App.test.jsx
65310275
✓ should get pdf (40 ms)
✓ should handle error (13 ms)
console.log
timeout
at CustomConsole.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 50 | 100 | 100 |
App.jsx | 100 | 50 | 100 | 100 | 11
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 6.095 s
source code: https://github.com/mrdulin/jest-v26-codelab/tree/main/examples/65310275

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

test restapi inside useeffect in jest

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

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

Resources