Шутка: Как отменить глобальный макет для определенных тестов в файле

#javascript #jestjs

#javascript #jestjs

Вопрос:

Я хочу издеваться над Math.random для определенных тестов и использовать его оригинальную реализацию для других тестов. Как я могу этого добиться? Я читал об использовании jest.doMock и jest.dontMock , но я столкнулся с рядом проблем, используя их, например:

  • Кажется, мне нужно require использовать doMock и dontMock , но мой проект использует только модули ES6 для импорта модулей
  • У этих функций также есть проблемы с использованием глобального модуля, например Math . Я получаю сообщение об ошибке при попытке использования jest.doMock("Math.random") , что приводит к Cannot find module 'Math' from 'app.test.js'

Мне не обязательно использовать doMock and dontMock для моих тестов. Они просто казались самым близким, что я мог найти в документации jest к тому, чего я хочу достичь. Но я открыт для альтернативных решений.

Мою функцию я хочу протестировать внутри app.js …

 export function getRandomId(max) {
    if (!Number.isInteger(max) || max <= 0) {
        throw new TypeError("Max is an invalid type");
    }
    return Math.floor(Math.random() * totalNumPeople)   1;
}
 

Внутри app.test.js …

 describe("getRandomId", () => {
  const max = 10;
  Math.random = jest.fn();

  test("Minimum value for an ID is 1", () => {
      Math.mockImplementationOnce(() => 0);
      const id = app.getRandomId(max);
      expect(id).toBeGreaterThanOrEqual(1);
  });

  test("Error thrown for invalid argument", () => {
      // I want to use the original implementation of Math.random here
      expect(() => getRandomId("invalid")).toThrow();
  })
});
 

Комментарии:

1. Вы смотрели на jestjs.io/docs/en/mock-function-api#mockfnmockreset и jestjs.io/docs/en/mock-functions#mocking-modules ?

Ответ №1:

Попробуйте это:

 describe("getRandomId", () => {
  const max = 10;
  let randomMock;

  beforeEach(() => {
    randomMock = jest.spyOn(global.Math, 'random');
  });

  test("Minimum value for an ID is 1", () => {
      randomMock.mockReturnValue(0);
      const id = getRandomId(max);
      expect(id).toBeGreaterThanOrEqual(1);
  });

  test("Error thrown for invalid argument", () => {
      // I want to use the original implementation of Math.random here
      randomMock.mockRestore(); // restores the original (non-mocked) implementation
      expect(() => getRandomId("invalid")).toThrow();
  })
});
 

Комментарии:

1. Спасибо, это было очень полезно! Однако я заметил странное поведение при использовании mockRestore во втором тесте. Из любопытства я хотел посмотреть, что Math.random вернулось после восстановления randomMock . Итак, я поместил a expect(randomMock).toHaveReturnedWith(20) , чтобы сделать тест неудачным и посмотреть, что Math.Random на самом деле вернулось. Как ни странно, тест не удался, потому Math.Random что вообще не вызывался.

2. Если я вместо этого использую mockReturnValueOnce(0) для первого теста и заменяю mockRestore на mockClear во втором тесте, проблема решена. Результаты показывают, что Math.Random он вызывается с его первоначальной реализацией. Вы случайно не знаете, почему mockRestore это привело к Math.Random тому, что не был вызван?

3. Причина, по которой он работает с вашей модификацией, заключается mockReturnValueOnce(0) в том, что он восстановит макет после первого вызова Math.random . Восстановление randomMock просто означает, что любые последующие вызовы Math.random() не будут издеваться. но randomMock он больше не используется, поскольку он потерял привязку к Math.random

4. Вы можете проверить, что ответ работает так, как задумано, добавив a console.log(getRandomId(42) в оба теста (после редактирования в первом тесте и после восстановления во втором тесте). Запуск теста всегда выводит 1 в первом тесте и случайное целое число (в заданных границах) во втором случае.

5. Это полезно знать! Я не понимал, что randomMock это потеряет свою привязку к Math.Random after mockRestore is вызывается. Я также смог проверить это поведение при запуске своих тестов. Спасибо за объяснение.