【问题标题】:Jest store.dispatch mock results in recursion loop and OOMJest store.dispatch 模拟导致递归循环和OOM
【发布时间】:2021-12-21 17:23:01
【问题描述】:

上下文:

我们有一个自定义计时器,它会在用户闲置 15 分钟后注销。计时器的 performLogoff 函数调用 store.dispatch 到用户模块的注销操作,以清理我们的 Vue.js 应用程序中的其余状态和 localforage。

问题:

  • 测试模拟store.dispatch 并调用performLogoff
  • performLogoff 调用 store.dispatch 进行注销
  • 问题 1:代码仍在 user/logout 内部
  • 后续问题 2:一旦遇到user/logout 中的第一个store.dispatch,它就会向下递归到自身并且永不终止

由于这些都是异步的,测试被标记为绿色/通过,但它不会终止。相反,它继续递归并最终耗尽内存,然后中止(见下面的输出)。

问题:

  • 如何正确模拟 store.dispatch 以便我们可以验证它是否已成功调用,而不会卡在此循环中?
timer.spec.js
import store from '@/store'
import userStore from '@/store/modules/user'

jest.mock('@/store')

...

  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
  ...

  async performLogoff() {
    await store.dispatch('user/logout')
  }
  ...
存储/模块/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)

【问题讨论】:

    标签: vue.js unit-testing jestjs vuex store


    【解决方案1】:

    mockImplementationOnce 更新了测试,它似乎工作了:

    ...
      it('should be able to log user off', async () => {
        let count = 0
    
        let mockCommit = () => {
          count += 1
        }
        localforage.removeItem = jest.fn(() => {
          return null
        })
    
        store.dispatch = jest.fn().mockImplementationOnce(async () => {
          await userStore.actions.logout({ commit: mockCommit })
        })
    
        return t.performLogoff().then(() => {
          expect(count).toBe(1)
        })
      })
    ...
    

    这允许第一次调用模拟,并且用户模块中的所有后续store.dispatch 调用都不会被模拟,从而停止递归循环。

    仍然愿意接受其他答案/更好的测试方法!

    【讨论】:

      猜你喜欢
      • 2017-03-03
      • 2011-04-01
      • 2015-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-07
      • 1970-01-01
      相关资源
      最近更新 更多