Jest mock store.dispatch引发递归循环与OOM问题的正确解决方法问询
let t
beforeAll(() => {
t = new Timer(...)
})
...
it('should be able to log user off', async () => {
jest.requireMock('@/store')
jest.requireMock('@/store/modules/user')
let count = 0 let mockCommit = () => { count += 1 } localforage.removeItem = jest.fn(() => { return null }) store.dispatch = jest.fn(async () => { await userStore.actions.logout({ commit: mockCommit }) }) return t.performLogoff().then(() => { expect(count).toBe(1) })
})
#### timer.js ```js ... async performLogoff() { await store.dispatch('user/logout') } ...
store/modules/user.js
... async logout({ commit }) { await localforage.removeItem('token') await localforage.removeItem('user_details') ... commit('SET_TOKEN', '') commit('SET_USER_DETAILS', '') ... store.dispatch('module2/setDetails', '') store.dispatch('module3/setOtherDetails', '') window.localStorage.clear() } ...
测试控制台输出
<--- Last few GCs ---> [50123:0x7fd300008000] 152839 ms: Mark-sweep (reduce) 4081.6 (4143.1) -> 4081.1 (4143.6) MB, 5073.3 / 0.0 ms (average mu = 0.290, current mu = 0.001) allocation failure scavenge might not succeed [50123:0x7fd300008000] 157867 ms: Mark-sweep (reduce) 4082.3 (4143.8) -> 4081.9 (4144.6) MB, 5023.3 / 0.0 ms (average mu = 0.168, current mu = 0.001) allocation failure scavenge might not succeed <--- JS stacktrace ---> FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory 1: 0x10f4cb70c node::Abort() [/usr/local/bin/node] 2: 0x10f4cc73d node::OnFatalError(char const*, char const*) [/usr/local/bin/node] 3: 0x10f62b88d v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/usr/local/bin/node] 4: 0x10f62b838 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/usr/local/bin/node] 5: 0x10f76f9e7 v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/usr/local/bin/node] 6: 0x10f76e9e5 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/usr/local/bin/node] 7: 0x10f77ad0d v8::internal::Heap::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/local/bin/node] 8: 0x10f77ad6c v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/local/bin/node] 9: 0x10f752308 v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [/usr/local/bin/node] 10: 0x10fa00771 v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [/usr/local/bin/node] 11: 0x10f3304b9 Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/usr/local/bin/node] 12: 0x10f3a8879 Builtins_NewStrictArgumentsElements [/usr/local/bin/node] 13: 0x1178a89d9 Process finished with exit code 134 (interrupted by signal 6: SIGABRT)
解决方案
根因分析
你当前的mock逻辑存在循环调用:
- 你将
store.dispatchmock为调用真实userStore.actions.logout的函数 - 而
user/logout内部又直接调用了store.dispatch触发其他模块的action - 此时调用
store.dispatch会再次触发你的mock逻辑,重新调用logout,形成无限递归
方案1:单测仅验证Timer职责(推荐)
Timer的单元测试只需要验证它正确调用了store.dispatch('user/logout')即可,logout内部逻辑属于用户模块的单测范围,不需要在Timer的测试中耦合:
import store from '@/store' jest.mock('@/store') let t beforeAll(() => { t = new Timer(...) }) it('should be able to log user off', async () => { // 清空之前的mock调用记录 store.dispatch.mockClear() // 直接mock为成功返回,不需要执行真实logout逻辑 store.dispatch.mockResolvedValue() await t.performLogoff() // 验证dispatch被正确调用 expect(store.dispatch).toHaveBeenCalledTimes(1) expect(store.dispatch).toHaveBeenCalledWith('user/logout') })
方案2:需要同时验证logout逻辑
如果确实要在当前测试中验证logout的执行逻辑,需要在mockstore.dispatch时按调用参数区分处理,仅当调用user/logout时执行真实逻辑,其他调用直接返回:
import store from '@/store' import userStore from '@/store/modules/user' jest.mock('@/store') ... it('should be able to log user off', async () => { let commitCount = 0 const mockCommit = () => { commitCount += 1 } localforage.removeItem = jest.fn().mockResolvedValue(null) // mock localStorage.clear避免影响全局环境 Storage.prototype.clear = jest.fn() store.dispatch.mockImplementation(async (actionName, payload) => { // 仅user/logout调用执行真实logout逻辑 if (actionName === 'user/logout') { return userStore.actions.logout({ commit: mockCommit }) } // 其他模块的action调用直接返回 return Promise.resolve() }) await t.performLogoff() // 原断言1错误,logout内调用了两次commit:SET_TOKEN、SET_USER_DETAILS expect(commitCount).toBe(2) // 可按需验证其他逻辑 expect(store.dispatch).toHaveBeenCalledWith('module2/setDetails', '') expect(store.dispatch).toHaveBeenCalledWith('module3/setOtherDetails', '') expect(localforage.removeItem).toHaveBeenCalledTimes(2) expect(localStorage.clear).toHaveBeenCalled() })
内容的提问来源于stack exchange,提问作者rishimaharaj
相关产品推荐
相关产品推荐

