I'm writing tests for my react page but my page uses an isLoading in its state, when loading the page renders 'Loading', when loaded but no data (from the fetch request) is renders 'No data found' and when loaded with data (from the fetch request) is loads the welcome page.
I want to write a test that checks the expected text is displayed when:
I think I have manged to have no data test working correctly but I'm have error messages when I try to mock the data it gets back. The error message is
TypeError: Cannot read property 'map' of undefined
But im not sure why my mock data is data: orders > which has data inside of it so shouldn't be undefined.
Can someone please tell me how to correct the test?
WelcomePage.js
import React from "react";
import { Link } from "react-router-dom";
class WelcomePage extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: false,
};
}
componentDidMount() {
this.setState({ isLoading: true });
const proxyurl = "https://cors-anywhere.herokuapp.com/";
const url =
"mu url"
fetch(proxyurl + url)
.then((res) => res.json())
.then((data) => this.setState({ data: data, isLoading: false }));
}
render() {
const { data, isLoading } = this.state;
if (isLoading) {
return (
<div className="pageLoading">
<p>Loading...</p>
</div>
);
}
if (data.length === 0) {
return <p> no data found</p>;
}
return (
<div className="Welcome">
<div className="Details">
<p className="userID">
<strong>
Welcome {data.userForename} {data.userSurname}
</strong>
</p>
<button className="SignOut">
<Link to={{ pathname: "/LogIn" }}>Sign Out</Link>
</button>
</div>
<div className="InfoBox">
<p>
<strong>Orders</strong>{" "}
</p>
</div>
<div className="orderss">
{data.orders.map((order, index) => (
<ul className="orderBox" key={order.toString()+index}>
<li className="orderLink">
<Link
to={{
pathname: "/OrderDetails",
state: {
orderNumber: order.rollNumber,
userID: this.state.userID,
},
}}
>
Order number: {order.rollNumber}
</Link>
</li>
<li> Customer name: {order.customerFullName}</li>
</ul>
))}
</div>
</div>
);
}
}
export default WelcomePage;
welcome.page.test.js
import React, { useState } from 'react';
import renderer from 'react-test-renderer';
import {render, unmountComponentAtNode } from 'react-dom';
import WelcomePage from './WelcomePage';
let container = null;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
})
afterEach(() => {
unmountComponentAtNode(container);
container.remove();
container = null;
})
test("test page renders as epxected when loading", () => {
const userId= "123456";
render(<WelcomePage location={userId}></UserWelcome>, container);
expect(container.innerHTML).toContain("Loading...");
});
test("mock fetch call, empty responce from db should return no data",
async () => {
const fakeResponse = [];
const userId = "1234567";
jest.spyOn(window, "fetch").mockImplementation(() => {
const fetchResponse = {
json: () => Promise.resolve(fakeResponse),
};
return Promise.resolve(fetchResponse);
});
await act(async () => {
render(<WelcomePage location={userId} />, container);
});
expect(container.innerHTML).toContain("no data");
window.fetch.mockRestore();
}
);
test("mock fetch call, valid responce from db should return applications", async () => {
//{ rates: { CAD: 1.42 } }
const fakeResponse = [
{
data: {
userId: 1234567,
userForename: "Joe",
userSurname: "Bloggs",
orders: [
{
orderNumber: 10000000001,
customerFullName: "Mrs Joes Bloggs",
userId: 1234567
},
]
}
}
];
const userId = "1234567";
jest.spyOn(window, "fetch").mockImplementation(() => {
const fetchResponse = {
json: () => Promise.resolve(fakeResponse)
};
return Promise.resolve(fetchResponse);
});
await act(async () => {
render(<WelcomePage location={userId} />, container);
});
expect(container.textContent).toContain("Order number: 10000000001");
window.fetch.mockRestore();
});
Edit - adding package.json
{
"name": "sm-app",
"version": "0.1.0",
"private": false,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"bootstrap": "^4.5.3",
"moment": "^2.29.1",
"prop-types": "^15.7.2",
"react": "^17.0.1",
"react-app-polyfill": "^2.0.0",
"react-bootstrap": "^1.4.0",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.4",
"react-spinners": "^0.9.0",
"reactstrap": "^8.6.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all",
"ie >= 9"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version",
"ie 11"
]
},
"devDependencies": {
"prettier": "2.1.2",
"react-test-renderer": "^17.0.1"
}
}
As you can see you passed empty array which does not contain orders
test("mock fetch call, empty responce from db should return no data",
async () => {
const fakeResponse = []; <-- empty array which does not contain orders
const userId = "1234567";
jest.spyOn(window, "fetch").mockImplementation(() => {
const fetchResponse = {
json: () => Promise.resolve(fakeResponse),
};
return Promise.resolve(fetchResponse);
});
await act(async () => {
render(<WelcomePage location={userId} />, container);
});
expect(container.innerHTML).toContain("no data");
window.fetch.mockRestore();
}
);
Because data is empty when it tried to access orders which is undefined
and undefined
does not have map
function
{data.orders.map((order, index) => ( <-- problems here
<ul className="orderBox" key={order.toString()+index}>
<li className="orderLink">
<Link
to={{
pathname: "/OrderDetails",
state: {
orderNumber: order.rollNumber,
userID: this.state.userID,
},
}}
>
Order number: {order.rollNumber}
</Link>
</li>
<li> Customer name: {order.customerFullName}</li>
</ul>
))}
You can change it like this:
{data && data.orders && data.orders.map((order, index) => ( <-- changes
<ul className="orderBox" key={order.toString()+index}>
<li className="orderLink">
<Link
to={{
pathname: "/OrderDetails",
state: {
orderNumber: order.rollNumber,
userID: this.state.userID,
},
}}
>
Order number: {order.rollNumber}
</Link>
</li>
<li> Customer name: {order.customerFullName}</li>
</ul>
))}
Note:
fakeResponse
as array you need to update setState
inside fetch in WelcomePage.js
toif (data && data[0] && data[0]['data']) {
this.setState({
data: data[0]['data'], <-- changes
isLoading: false,
});
}
fakeResponse
to:const fakeResponse = {
data: {
userId: 1234567,
userForename: "Joe",
userSurname: "Bloggs",
orders: [
{
orderNumber: 10000000001,
customerFullName: "Mrs Joes Bloggs",
userId: 1234567
},
]
}
};
setState to:
if (data && data['data']) {
this.setState({
data: data['data'], <-- changes
isLoading: false,
});
}
And condition to check if data is empty:
if (Object.keys(data).length === 0) {
return <p> no data found</p>;
}