如何模拟在使用Jest测试的React组件中进行的API调用

时间:2019-04-12 18:58:40

标签: javascript reactjs jestjs react-testing-library

我正在尝试模拟将数据检索到组件中的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;
    })
  );

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

component / 测试 /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();
});

4 个答案:

答案 0 :(得分:3)

默认情况下,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);

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

答案 1 :(得分:0)

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

Jest做了一些提升魔术,使您可以用模拟替换导入的模块,但是似乎确实需要这些特殊的变量名前缀来完​​成它的工作。

答案 2 :(得分:0)

这是一个广义答案。首先,在测试内部进行jest.mock(../service)

然后转到您的服务文件,在其中进行呼叫。创建一个__mocks__文件,然后在内部创建一个与您的服务文件相同的文件。

然后为您想要的每个服务创建函数,这些函数可以解析承诺以与常规调用一样异步。

const getUser = () => promise.resolve()

以上是一般示例。如果需要数据,请创建一个您自己选择的模拟数据对象/数组,并使用该函数为其提供服务。

答案 3 :(得分:0)

不要嘲笑进行 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 进行更深入的探索。