Jest的spyOn期间发生TypeError:无法设置仅具有吸气剂的#<object>的属性getRequest

时间:2018-11-05 20:49:54

标签: reactjs unit-testing jestjs spy spyon

我正在用TypeScript编写React应用程序。我使用Jest进行单元测试。

我有一个可以进行API调用的函数:

import { ROUTE_INT_QUESTIONS } from "../../../config/constants/routes";
import { intQuestionSchema } from "../../../config/schemas/intQuestions";
import { getRequest } from "../../utils/serverRequests";

const intQuestionListSchema = [intQuestionSchema];

export const getIntQuestionList = () => getRequest(ROUTE_INT_QUESTIONS, intQuestionListSchema);

getRequest函数如下所示:

import { Schema } from "normalizr";
import { camelizeAndNormalize } from "../../core";

export const getRequest = (fullUrlRoute: string, schema: Schema) =>
  fetch(fullUrlRoute).then(response =>
    response.json().then(json => {
      if (!response.ok) {
        return Promise.reject(json);
      }
      return Promise.resolve(camelizeAndNormalize(json, schema));
    })
  );

我想像这样使用Jest尝试API函数:

import fetch from "jest-fetch-mock";
import { ROUTE_INT_QUESTIONS } from "../../../config/constants/routes";
import {
  normalizedIntQuestionListResponse as expected,
  rawIntQuestionListResponse as response
} from "../../../config/fixtures";
import { intQuestionSchema } from "../../../config/schemas/intQuestions";
import * as serverRequests from "./../../utils/serverRequests";
import { getIntQuestionList } from "./intQuestions";

const intQuestionListSchema = [intQuestionSchema];

describe("getIntQuestionList", () => {
  beforeEach(() => {
    fetch.resetMocks();
  });

  it("should get the int question list", () => {
    const getRequestMock = jest.spyOn(serverRequests, "getRequest");
    fetch.mockResponseOnce(JSON.stringify(response));

    expect.assertions(2);
    return getIntQuestionList().then(res => {
      expect(res).toEqual(expected);
      expect(getRequestMock).toHaveBeenCalledWith(ROUTE_INT_QUESTIONS, intQuestionListSchema);
    });
  });
});

问题是带有spyOn的行引发以下错误:

  ● getRestaurantList › should get the restaurant list

    TypeError: Cannot set property getRequest of #<Object> which has only a getter

      17 |
      18 |   it("should get the restaurant list", () => {
    > 19 |     const getRequestMock = jest.spyOn(serverRequests, "getRequest");
         |                                 ^
      20 |     fetch.mockResponseOnce(JSON.stringify(response));
      21 |
      22 |     expect.assertions(2);

      at ModuleMockerClass.spyOn (node_modules/jest-mock/build/index.js:706:26)
      at Object.spyOn (src/services/api/IntQuestions/intQuestions.test.ts:19:33)

我在Google上搜索了此信息,但只找到了有关热重装的帖子。那么在Jest测试期间可能导致什么呢?我怎样才能通过这项考试?

7 个答案:

答案 0 :(得分:7)

使用ts-jest作为编译器进行测试,如果您以这种方式模拟模块,它将可以正常工作:

import * as serverRequests from "./../../utils/serverRequests";

jest.mock('./../../utils/serverRequests', () => ({
  __esModule: true,
  ...jest.requireActual('./../../utils/serverRequests')
}));

const getRequestMock = jest.spyOn(serverRequests, "getRequest");

Jest oficial doc for __esModule

答案 1 :(得分:6)

这很有趣。

问题

Babel生成的属性仅为重新导出的函数定义了get

utils/serverRequests/index.ts从其他模块重新导出功能,因此当使用jest.spyOn监视重新导出的功能时会引发错误。


详细信息

鉴于此代码,重新导出了lib中的所有内容:

export * from './lib';

... Babel产生此结果:

'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _lib = require('./lib');

Object.keys(_lib).forEach(function (key) {
  if (key === "default" || key === "__esModule") return;
  Object.defineProperty(exports, key, {
    enumerable: true,
    get: function get() {
      return _lib[key];
    }
  });
});

请注意,所有属性都仅由get定义。

尝试在其中任何一个属性上使用jest.spyOn都会产生您所看到的错误,因为jest.spyOn试图用包裹原始函数的间谍替换该属性,但是如果定义了该属性则无法仅get


解决方案

不是将../../utils/serverRequests(重新导出getRequest)导入测试,而是导入定义了getRequest的模块,并使用该模块创建间谍。

替代解决方案

按照@Volodymyr和@TheF的建议模拟整个utils/serverRequests模块

答案 2 :(得分:4)

最近,我们在使用的库中遇到了类似的情况。 Babel只为从库中导出的所有成员提供吸气剂,因此我们在测试的顶部进行了此操作:

jest.mock('some-library', () => ({
  ...jest.requireActual('some-library')
}));

此问题得以解决,因为它创建了一个新的普通JS对象,其中包含库中每个属性的成员。

答案 3 :(得分:2)

正如评论中所建议的,jest要求在被测试对象上有一个setter,而es6模块对象没有。 jest.mock()允许您通过在导入后模拟所需的模块来解决此问题。

尝试模拟您的serverRequests文件中的导出

import * as serverRequests from './../../utils/serverRequests';
jest.mock('./../../utils/serverRequests', () => ({
    getRequest: jest.fn()
}));

// ...
// ...

it("should get the int question list", () => {
    const getRequestMock = jest.spyOn(serverRequests, "getRequest")
    fetch.mockResponseOnce(JSON.stringify(response));

    expect.assertions(2);
    return getIntQuestionList().then(res => {
        expect(res).toEqual(expected);
          expect(getRequestMock).toHaveBeenCalledWith(ROUTE_INT_QUESTIONS, intQuestionListSchema);
    });
});

以下是一些有用的链接:
https://jestjs.io/docs/en/es6-class-mocks
https://jestjs.io/docs/en/mock-functions

答案 4 :(得分:2)

对于其他有此问题的人,您可以将babel设置为使用“松散”转换,这为我解决了该问题。只需在.babelrc文件中将其设置为

$(".nav-link.dropdown-toggle").click(function() {
    $(this).closest('.nav-item.dropdown').removeClass('dropdown-toggle-arrow');  
    $(this).closest('.nav-item.dropdown').toggleClass('dropdown-toggle-arrow');     
});

答案 5 :(得分:1)

交叉时,Jest单元测试的升级失败:

export * from './serverRequests';

直接引用文件,以避免出现“ ...只有吸气剂”的问题!

答案 6 :(得分:0)

如果您希望不修改导入内容,则可以通过以下方式解决此问题:

import * as lib from './lib'

jest.mock('./lib/subModule')

it('can be mocked', () => {
  jest.spyOn(api, 'subModuleFunction')
})

您需要为要监视的任何其他重新导出的功能添加更多jest.mock行。