Ecmascript 6 如何用jest模拟redux操作依赖性的实现

Ecmascript 6 如何用jest模拟redux操作依赖性的实现,ecmascript-6,redux,jestjs,Ecmascript 6,Redux,Jestjs,我在Jest中遇到了问题,因为它没有提升前缀为“mock”的mock函数 我的理解是,这应该按照 我有一个redux操作,它使用另一个依赖项执行某些操作。 然后,对依赖模块调用方法的结果将与另一个操作一起调度 如何在从属模块AuthUtils中模拟resume的实现。调用thunk会引发错误,因为resume方法未定义 Actions.js import { setUser } from '../../src/actions/UserActions'; import AuthUtils from

我在Jest中遇到了问题,因为它没有提升前缀为“mock”的mock函数 我的理解是,这应该按照

我有一个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() {

  }
};
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
  });
});
Actions.test.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() {

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

在这种情况下,我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
函数的模拟和设置移动到从
authoctions
导入之前:

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模拟函数(和SPIE)有一个方便的函数来模拟承诺结果,因此您不需要手动调用
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方法中,然后低看,一切都按预期进行。我把它放在方法之外,因为有更多的操作,它们只是共享同一个实例,但是每次初始化一个新实例并没有真正的缺点。一些非常好的建议,非常感谢你的回答,瑞克