如何在我的反应测试中模拟状态和数据的价值

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

我正在为我的 React 页面编写测试,但我的页面在其状态下使用 isLoading,当加载页面时呈现 'Loading',当加载但没有数据(来自获取请求)时呈现 'No data found' 并在加载数据(来自获取请求)时加载欢迎页面。

我想编写一个测试来检查在以下情况下是否显示预期的文本:

我想我已经设法让没有数据测试正常工作,但是当我尝试模拟它返回的数据时出现错误消息。错误信息是

TypeError: Cannot read property 'map' of undefined

但我不确定为什么我的模拟数据是 data: orders > 其中有数据,所以不应该是未定义的。

有人可以告诉我如何改正测试吗?

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();
});

编辑 - 添加 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"
  }
}

fakeRespose 是对象数组。

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

所以下面的传球。

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

但是 data 是数组,所以 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();
  }
);

因为当它尝试访问 undefinedundefined 没有 map 功能的订单时数据是空的

{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>
))}

你可以这样改:

{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>
))}

注:


  • 当您将 fakeResponse 作为数组传递时,您需要将 WelcomePage.js 中的 fetch 中的 setState 更新为
if (data && data[0] && data[0]['data']) {
    this.setState({
        data: data[0]['data'], <-- changes
        isLoading: false,
    });
}

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

将状态设置为:

if (data && data['data']) {
    this.setState({
        data: data['data'], <-- changes
        isLoading: false,
    });
}

以及检查数据是否为空的条件:

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