【问题标题】:How to mock API calls made within a React component being tested with Jest如何模拟在使用 Jest 测试的 React 组件中进行的 API 调用
【发布时间】:2019-09-03 14:14:20
【问题描述】:

我正在尝试模拟一个将数据检索到组件中的 fetch()。

I'm using this as a model for mocking my fetches,但我无法让它工作。

我在运行测试时收到此错误:babel-plugin-jest-hoist: The module factory of 'jest.mock()' is not allowed to reference any out-of-scope variables.

有没有办法让这些函数返回模拟数据,而不是实际尝试进行真正的 API 调用?

代码

utils/getUsers.js

返回角色映射到每个用户的用户。

const getUsersWithRoles = rolesList =>
  fetch(`/users`, {
    credentials: "include"
  }).then(response =>
    response.json().then(d => {
      const newUsersWithRoles = d.result.map(user => ({
        ...user,
        roles: rolesList.filter(role => user.roles.indexOf(role.iden) !== -1)
      }));
      return newUsersWithRoles;
    })
  );

组件/UserTable.js

const UserTable = () => {
  const [users, setUsers] = useState([]);
  useEffect(() => {
    getTableData();
  }, []);

  const getTableData = () => {
    new Promise((res, rej) => res(getRoles()))
      .then(roles => getUsersWithRoles(roles))
      .then(users => {
        setUsers(users);
      });
  };
  return (...)
};

组件/测试/UserTable.test.js

import "jest-dom/extend-expect";
import React from "react";
import { render } from "react-testing-library";
import UserTable from "../UserTable";
import { getRoles as mockGetRoles } from "../utils/roleUtils";
import { getUsersWithRoles as mockGetUsersWithRoles } from "../utils/userUtils";

const users = [
  {
    name: "Benglish",
    iden: "63fea823365f1c81fad234abdf5a1f43",
    roles: ["eaac4d45c3c41f449cf7c94622afacbc"]
  }
];

const roles = [
  {
    iden: "b70e1fa11ae089b74731a628f2a9b126",
    name: "senior dev"
  },
  {
    iden: "eaac4d45c3c41f449cf7c94622afacbc",
    name: "dev"
  }
];

const usersWithRoles = [
  {
    name: "Benglish",
    iden: "63fea823365f1c81fad234abdf5a1f43",
    roles: [
      {
        iden: "eaac4d45c3c41f449cf7c94622afacbc",
        name: "dev"
      }
    ]
  }
];

jest.mock("../utils/userUtils", () => ({
  getUsers: jest.fn(() => Promise.resolve(users))
}));
jest.mock("../utils/roleUtils", () => ({
  getRolesWithUsers: jest.fn(() => Promise.resolve(usersWithRoles)),
  getRoles: jest.fn(() => Promise.resolve(roles))
}));

test("<UserTable/> show users", () => {
  const { queryByText } = render(<UserTable />);

  expect(queryByText("Billy")).toBeTruthy();
});

【问题讨论】:

    标签: javascript reactjs jestjs react-testing-library


    【解决方案1】:

    默认情况下,jest.mock 调用由 babel-jest 提升...

    ...这意味着它们在测试文件中的任何其他内容之前运行,因此在测试文件中声明的任何变量都不会在范围内。

    这就是为什么传递给jest.mock 的模块工厂不能引用自身之外的任何东西。


    一种选择是将数据移入模块工厂,如下所示:

    jest.mock("../utils/userUtils", () => {
      const users = [ /* mock users data */ ];
      return {
        getUsers: jest.fn(() => Promise.resolve(users))
      };
    });
    jest.mock("../utils/roleUtils", () => {
      const roles = [ /* mock roles data */ ];
      const usersWithRoles = [ /* mock usersWithRoles data */ ];
      return {
        getRolesWithUsers: jest.fn(() => Promise.resolve(usersWithRoles)),
        getRoles: jest.fn(() => Promise.resolve(roles))
      };
    });
    

    另一种选择是使用 jest.spyOn 模拟函数:

    import * as userUtils from '../utils/userUtils';
    import * as roleUtils from '../utils/roleUtils';
    
    const users = [ /* mock users data */ ];
    const roles = [ /* mock roles data */ ];
    const usersWithRoles = [ /* mock usersWithRoles data */ ];
    
    const mockGetUsers = jest.spyOn(userUtils, 'getUsers');
    mockGetUsers.mockResolvedValue(users);
    
    const mockGetRolesWithUsers = jest.spyOn(roleUtils, 'getRolesWithUsers');
    mockGetRolesWithUsers.mockResolvedValue(usersWithRoles);
    
    const mockGetRoles = jest.spyOn(roleUtils, 'getRoles');
    mockGetRoles.mockResolvedValue(roles);
    

    另一种选择是自动模拟模块:

    import * as userUtils from '../utils/userUtils';
    import * as roleUtils from '../utils/roleUtils';
    
    jest.mock('../utils/userUtils');
    jest.mock('../utils/roleUtils');
    
    const users = [ /* mock users data */ ];
    const roles = [ /* mock roles data */ ];
    const usersWithRoles = [ /* mock usersWithRoles data */ ];
    
    userUtils.getUsers.mockResolvedValue(users);
    roleUtils.getRolesWithUsers.mockResolvedValue(usersWithRoles);
    roleUtils.getRoles.mockResolvedValue(roles);
    

    ...并将模拟的响应添加到空的模拟函数中。

    【讨论】:

    • 嗯。这似乎对我不起作用。是因为我试图模拟 npm 包吗?我使用自动生成的 TS 客户端来处理我的一个 API,该 API 在被测组件中使用
    • @SteveBoniface 所有这些策略都适用于用户模块和节点模块 (npm),所以这可能是一个不同的问题
    • 好的。好吧,FWIW,我从同一个模块导入了几个不同的东西。两者都在被测组件中使用,在这些类上调用它们的构造函数和方法。我已经做得够多了,可以为这两个依赖项返回一个 mockConstructor,但是我如何才能真正模拟这些类上的函数实现呢?
    【解决方案2】:

    不要模拟进行 API 调用的工具;存根服务器响应。下面是我将如何使用名为 nock 的 HTTP 拦截器重写您的测试。

    import "jest-dom/extend-expect";
    import React from "react";
    import { render, waitFor } from "react-testing-library";
    import UserTable from "../UserTable";
    
    const users = [
      {
        name: "Benglish",
        iden: "63fea823365f1c81fad234abdf5a1f43",
        roles: ["eaac4d45c3c41f449cf7c94622afacbc"]
      }
    ];
    
    const roles = [
      {
        iden: "b70e1fa11ae089b74731a628f2a9b126",
        name: "senior dev"
      },
      {
        iden: "eaac4d45c3c41f449cf7c94622afacbc",
        name: "dev"
      }
    ];
    
    const usersWithRoles = [
      {
        name: "Benglish",
        iden: "63fea823365f1c81fad234abdf5a1f43",
        roles: [
          {
            iden: "eaac4d45c3c41f449cf7c94622afacbc",
            name: "dev"
          }
        ]
      }
    ];
    
    describe("<UserTable/>", () => {
      it("shows users", async () => { // <-- Async to let nock kick over resolved promise
        nock(`${server}`)
          .get('/users')
          .reply(200, {
            data: users
          })
          .get('/usersWithRoles')
          .reply(200, {
            data: usersWithRoles
          })
          .get('/roles')
          .reply(200, {
            data: roles
          });
        const { queryByText } = render(<UserTable />);
    
        await waitFor(() => expect(queryByText("Billy")).toBeTruthy()); // <-- Is this supposed to be "Benglish"?
      });
    });
    

    现在您的测试套件不知道您是如何获取数据的,并且您不必维护复杂的模拟。查看Testing Components that make API calls 进行更深入的了解。

    【讨论】:

      【解决方案3】:

      您需要将模拟范围中使用的变量重命名为前缀为mock(例如mockUsers)。

      Jest 做了一些提升魔法,允许您用模拟替换导入的模块,但似乎确实需要这些特殊的变量名前缀来做它的事情。

      【讨论】:

        猜你喜欢
        • 2020-10-08
        • 2020-09-16
        • 2021-12-10
        • 2020-05-17
        • 2016-07-04
        • 2016-03-05
        • 2019-02-24
        • 1970-01-01
        相关资源
        最近更新 更多