How can mock the value of a state and data in my react test

18.3k Views Asked by At

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:

  • The page is loaded but no data is found
  • The page is loaded and has data

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"
  }
}
2

There are 2 best solutions below

0
On BEST ANSWER

Test case

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:


  • As you are passing fakeResponse as array you need to update setState inside fetch in WelcomePage.js to
if (data && data[0] && data[0]['data']) {
    this.setState({
        data: data[0]['data'], <-- changes
        isLoading: false,
    });
}

  • or change 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>;
}
0
On

fakeRespose is array of object.

  const fakeResponse = [
    {
      data: {
        userId: 1234567,
        userForename: "Joe",
        userSurname: "Bloggs",
        orders: [
          {
            orderNumber: 10000000001,
            customerFullName: "Mrs Joes Bloggs",
            userId: 1234567
          },
        ]
      }
    }
  ];

So following passes.

    if (data.length === 0) {
      return <p> no data found</p>;
    }

But data is array so orders doesn't exist.