61

I'm writing a React application with TypeScript. I do my unit tests using Jest.

I have a function that makes an API call:

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

The getRequest function looks like this:

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

I wanted to try the API function using Jest like this:

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

The problem is that the line with spyOn throws the following error:

  ● 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)

I googled this and only found posts about hot reloading. So what could cause this during Jest test? How can I get this test to pass?


Symbolic definite integral expression does not yield the same result as numerical evaluation

I resolved the definite integral of an expression using sympy in order to get the symbolic expression of the integral. However, when I use the yielded expression in a function, I do not get the same result as given by the numerical evaluation of the integral:

>> from sympy import *
>> x, y, a, b, c, d, k = symbols ('x y a b c d k', positive=True)
>> res = integrate(exp(-k*abs(x-y)), (x, a, b), (y, c, d))

>> res
(-exp(a*k) + exp(b*k))*exp(-b*k)*exp(-k*(a - d))/k**2 - (-exp(a*k) + exp(b*k))*exp(-b*k)*exp(-k*(a - c))/k**2

>> def integral_1(k1, a1, b1, c1, d1):
>>     return (-exp(a1*k1) + exp(b1*k1))*exp(-b1*k1)*exp(-k1*(a1 - d1))/k1**2 - (-exp(a1*k1) + exp(b1*k1))*exp(-b1*k1)*exp(-k1*(a1 - c1))/k1**2

>> integral_1(0.6, 0, 1, 0, 1)
1.0303623235681536

>> integrate(exp(-0.6*abs(x-y)), (x, 0, 1), (y, 0, 1))
0.826731311633480

Why do I get such difference?

4

7 回答 7

82

这个很有趣。

问题

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

utils/serverRequests/index.tsjest.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定义的模块并使用该模块来创建间谍。

替代解决方案

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

于 2018-11-14T19:53:09.353 回答
26

正如评论中所建议的,jest 需要在 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

于 2018-11-08T14:30:37.280 回答
22

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

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

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

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

开玩笑的官方文档__esModule

于 2020-08-12T10:01:58.110 回答
10

最近我们在我们正在使用的库中遇到了这样的事情。Babel 只为从库中导出的所有成员提供 getter,所以我们在测试的顶部这样做:

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

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

于 2020-08-03T20:27:21.423 回答
3

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

{
  "presets": [
    ["@babel/preset-env", {
      "loose": true
    }]
  ]
}
于 2020-05-15T04:20:54.630 回答
2

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

import * as lib from './lib'

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

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

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

于 2020-12-09T19:55:47.390 回答
1

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

export * from './serverRequests';

直接引用文件以避免“...只有一个吸气剂”问题!

于 2020-06-20T08:01:29.003 回答