Как смоделировать реализацию зависимости редукционных действий с помощью jest - PullRequest
0 голосов
/ 19 марта 2020

У меня проблемы с Jest, не поднимающим фиктивные функции, объявленные с префиксом 'mock'. Насколько я понимаю, это должно работать в соответствии с jest docs

У меня есть избыточные действия, которые делают что-то с другой зависимостью. Результат вызова метода в зависимом модуле затем отправляется с другим действием.

Как я могу посмеяться над реализацией resume в зависимом модуле AuthUtils. Вызов thunk вызывает ошибку, потому что метод resume не определен

Actions.js

import { setUser } from '../../src/actions/UserActions';
import AuthUtils from '../utils/AuthUtils'; //dependent es6 class
const auth = new AuthUtils(); 

export const resumeSession = () => async (dispatch, getState) => {
  try {
    const resumeResult = await auth.resume(); // wait for result
    dispatch(setUser(resumeResult)); //dispatch setUser with result
  } catch() {

  }
};

Actions.test.js:

import { resumeSession } from '../../src/actions/AuthActions';
import { setUser } from '../../src/actions/UserActions';

// auto mock UserActions
jest.mock('../../src/utils/UserActions');

// Mock resume method of AuthUtils using module factory param
// The mockResume here is undefined, but I expected because it begins with mock it would be hoisted along with the jest.mock call
// "An exception is made for variables that start with the word 'mock'." -- from the docks

const mockResume = jest.fn(() => Promise.resolve({ user: { things } }));
jest.mock('../../src/utils/AuthUtils', () => {
  return jest.fn().mockImplementation(() => {
    return { resume: mockResume };
  });
});

describe('resumeSession', () => {
  it('dispatches complete', async () => {
     const mockDispatch = jest.fn();
     const mockGetState = jest.fn();
     await resumeSession()(mockDispatch, mockGetState);
     expect(setUser).toHaveBeenCalledWith({ user: { things } });
     // Test blows up because AuthUtils#resume is not a function
  });
});

1 Ответ

1 голос
/ 20 марта 2020

В этом случае я на 99% уверен, что проблема в том, что вы издеваетесь слишком поздно.

const auth = new AuthUtils(); - это встроенный код в файле модуля. Это означает, что он выполняется сразу после импорта файла.

Ваш тестовый файл запускает код в следующем порядке:

import { resumeSession } from '../../src/actions/AuthActions';
// this does:
//     import AuthUtils from '../utils/AuthUtils';
//     const auth = new AuthUtils(); 
import { setUser } from '../../src/actions/UserActions';

jest.mock('../../src/utils/UserActions');

const mockResume = jest.fn(() => Promise.resolve({ user: { things } }));
jest.mock('../../src/utils/AuthUtils', () => {
  return jest.fn().mockImplementation(() => {
    return { resume: mockResume };
  });
});
// too late, since the code from the *actual* AuthUtils has already been executed

Этот будет работать нормально, если auth была локальной переменной в вашей функции resumeSession, например:

export const resumeSession = () => async (dispatch, getState) => {
  const auth = new AuthUtils();

  try {
    const resumeResult = await auth.resume(); // wait for result
    dispatch(setUser(resumeResult)); //dispatch setUser with result
  } catch() {

  }
};

Потому что тогда макет настраивается до того, как какой-либо код попытается использовать AuthUtils. Но я предполагаю, что вы по какой-то причине создаете auth вне функции.

Если перемещение экземпляра auth внутрь вашей функции не вариант, одно из возможных решений - вместо этого перенести макет и настроить AuthUtils и его функция resume для до вашего импорта из AuthActions:

const mockResume = jest.fn(() => Promise.resolve({ user: { things } }));
jest.mock('../../src/utils/AuthUtils', () => {
  return jest.fn().mockImplementation(() => {
    return { resume: mockResume };
  });
});

import { resumeSession } from '../../src/actions/AuthActions';
import { setUser } from '../../src/actions/UserActions';

jest.mock('../../src/utils/UserActions');

Если это не сработает (или если вы предпочитаете не иметь никакого кода раньше ваш импорт), другой вариант - экспортировать переменную auth, чтобы вы могли шпионить за фактическим экземпляром и высмеивать его resume функцию:

import { auth, resumeSession } from '../../src/actions/AuthActions';

const mockResume = jest.fn(() => Promise.resolve({ user: { things } }));
jest.spyOn(auth, "resume").mockImplementation(mockResume);

Эта может иметь сторону эффект сохранения вашей проверенной реализации для других тестов после того, как этот будет сделан, что вы, вероятно, не хотите. Вы можете использовать методы жизненного цикла Jest, чтобы избежать этого и восстановить первоначальную реализацию resume, когда ваши тесты будут завершены:

const mockResume = jest.fn(() => Promise.resolve({ user: { things } }));
const resumeSpy = jest.spyOn(auth, "resume");
resumeSpy.mockImplementation(mockResume);

describe('resumeSession', () => {
  afterAll(() => {
    resumeSpy.mockRestore();
  });

  it('dispatches complete', async () => {
     const mockDispatch = jest.fn();
     const mockGetState = jest.fn();
     await resumeSession()(mockDispatch, mockGetState);
     expect(setUser).toHaveBeenCalledWith({ user: { things } });
  });
});

Несвязанный sidenote: Функции Jest Mock (и шпионы) имеют удобную функцию для фиктивные результаты Promise, поэтому вам не нужно иметь фиктивную реализацию, которая вручную вызывает Promise.resolve() или Promise.reject(). Я лично предпочитаю использовать собственные функции Jest:

const mockResume = jest.fn();
mockResume.mockResolvedValue({ user: { things } }));

Если вы используете шпионский подход, вы можете вообще отказаться от функции mockResume:

const resumeSpy = jest.spyOn(auth, "resume");
resumeSpy.mockResolvedValue({ user: { things } }));

Это не связано с проблемой у вас сейчас есть, но я решил выбросить это туда.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...