You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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逻辑存在循环调用:

  1. 你将store.dispatch mock为调用真实userStore.actions.logout的函数
  2. 而user/logout内部又直接调用了store.dispatch触发其他模块的action
  3. 此时调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 11:54:04