【问题标题】:How to mock implementation of dependency of redux actions with jest如何用 jest 模拟 redux 操作的依赖关系的实现
【发布时间】:2020-03-20 08:52:33
【问题描述】:

我在 Jest 没有提升以“mock”为前缀声明的模拟函数时遇到问题 据我了解,这应该根据jest docs 工作

我有一个 redux 操作,它对另一个依赖项做了一些事情。 在依赖模块上调用方法的结果随后会与另一个操作一起分派。

如何在依赖模块AuthUtils 中模拟resume 的实现。调用 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
  });
});

【问题讨论】:

    标签: ecmascript-6 redux jestjs


    【解决方案1】:

    在这种情况下,我 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 } });
      });
    });
    

    不相关的旁注:Jest 模拟函数(和间谍)有一个方便的函数来模拟 Promise 结果,因此您不需要手动调用 Promise.resolve()Promise.reject() 的模拟实现。我个人更喜欢使用 Jest 自己的函数:

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

    如果你使用 spy 方法,你可以完全放弃 mockResume 函数:

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

    这与您当前遇到的问题无关,但我想我会把它扔掉。

    【讨论】:

    • 绝对正确,我将 AuthUtils 类的初始化移到了 resume 方法中,并且看到一切都按预期工作。我把它放在方法之外,因为有更多的动作,它们只是共享同一个实例,但每个初始化一个新实例并没有真正的缺点。一些非常棒的指点,非常感谢你的回答 Rick
    猜你喜欢
    • 2017-09-05
    • 2018-04-09
    • 2020-01-11
    • 1970-01-01
    • 2018-07-23
    • 2021-12-01
    • 2019-04-04
    • 2020-11-17
    • 2020-09-03
    相关资源
    最近更新 更多