web-dev-qa-db-fra.com

TypeError au cours du spyOn de Jest: impossible de définir la propriété getRequest de # <Object> qui n'a qu'un getter

J'écris une application React avec TypeScript. Je fais mes tests unitaires en utilisant Jest.

J'ai une fonction qui fait un appel 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);

La fonction getRequest ressemble à ceci:

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

Je voulais essayer la fonction API en utilisant Jest comme ceci:

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

Le problème est que la ligne avec spyOn lève l'erreur suivante:

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

Je l'ai cherché sur Google et n'ai trouvé que des articles sur le rechargement à chaud. Alors, qu'est-ce qui pourrait causer cela pendant le test Jest? Comment puis-je réussir ce test?

7
J. Hesters

Celui-ci était intéressant.

Problème

Babel génère des propriétés avec uniquement get défini pour les fonctions réexportées.

utils/serverRequests/index.ts réexporte des fonctions à partir d'autres modules afin qu'une erreur soit générée lorsque jest.spyOn est utilisé pour espionner les fonctions réexportées.


Détails

Étant donné que ce code réexporte tout de lib:

export * from './lib';

...Babel produit ceci:

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

Notez que les propriétés sont toutes définies avec uniquement get.

Essayer d'utiliser jest.spyOn sur l'une de ces propriétés générera l'erreur que vous voyez, car jest.spyOn tente de remplacer la propriété par un espion enveloppant la fonction d'origine, mais ne le pourra pas si la propriété est définie avec seulement get.


Solution

Au lieu d'importer ../../utils/serverRequests (qui réexporte getRequest) dans le test, importez le module dans lequel getRequest est défini et utilisez-le pour créer l'espion.

Solution alternative

Simulez le module utils/serverRequests complet comme suggéré par @Volodymyr et @TheF

5

Comme suggéré dans les commentaires, jest nécessite un objet de définition sur l'objet testé, ce que les objets de module es6 ne possèdent pas. jest.mock() vous permet de résoudre ce problème en moquant votre module requis après l'importation.

Essayez de vous moquer des exportations à partir de votre fichier 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);
    });
});

Voici quelques liens utiles: 
https://jestjs.io/docs/en/es6-class-mocks
https://jestjs.io/docs/en/mock-functions

1
The F