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

Vitest异步Mock返回Undefined:如何正确模拟getDisclosuresListAsync

问题:Vue3组件onMounted异步调用单元测试模拟失败

我正在为Vue 3组件的onMounted生命周期钩子编写单元测试,尝试模拟异步调用的resolved值,但它始终返回undefined,导致最后一个断言失败(期望接收3,实际接收0)。请问如何模拟getDisclosuresListAsync使其返回布尔值?

组件的onMounted代码

onMounted(async () => {
  applicationStore.updateShowSpinner(true);
  if (await disclosureStore.getDisclosuresListAsync()) {
    disclosures.value = disclosureStore.disclosures;
    if (
      disclosures.value.length > 0 &&
      disclosures.value.length === selectedDisclosures.value.length
    ) {
      emit('form-status-updated', true, null);
    }
    applicationStore.updateShowSpinner(false);
    goTo(0, { duration: 200, easing: 'linear' });
  }
});

单元测试代码

it('should get disclosures from the disclosureStore and populates the component disclosures on the onMounted lifecycle', async () => {
  // Arrange
  const appStore = mockedStore(useApplicationStore);
  appStore.updateShowSpinner.mockImplementation(() => {});

  const disclosureStore = mockedStore(useDisclosureStore);
  disclosureStore.getDisclosuresListAsync.mockResolvedValue(true);
  wrapper.unmount();

  // Act
  wrapper = mount(BeginDisclosures, {
    global: {
      plugins: [
        createTestingPinia({
          initialState: {
            appStore: {
              //...removed for clarity
            },
            disclosureStore: {
              disclosures: [
                <IDisclosure>{
                  id: 1,
                  name: 'Disclosure 1',
                  displayName: 'Disclosure 1',
                  url: 'http://localhost/api/disclosures/disclosure-1',
                },
                <IDisclosure>{
                  id: 2,
                  name: 'Disclosure 2',
                  displayName: 'Disclosure 2',
                  url: 'http://localhost/api/disclosures/disclosure-2',
                },
                <IDisclosure>{
                  id: 3,
                  name: 'Disclosure 3',
                  displayName: 'Disclosure 3',
                  url: 'http://localhost/api/disclosures/disclosure-3',
                },
              ],
              selectedDisclosures: [],
              selectedDisclosuresReset: false
            }
          },
          createSpy: vi.fn
        }),
        vuetify
      ]
    }
  });
  await wrapper.vm.$nextTick();
  const disclosures = wrapper.vm.disclosures;

  // Assert
  expect(disclosureStore.getDisclosuresListAsync).toHaveBeenCalled();
  expect(disclosures.length).equals(3)
});

解决方案

问题出在你mock store方法的时机和方式上:你提前创建的模拟store对象,和组件通过Pinia实际使用的store实例不是同一个,导致mock不生效,getDisclosuresListAsync实际返回undefined,代码里的if分支没执行,所以组件的disclosures还是空数组。

按下面的方式修改测试代码即可:

修改后的测试代码

it('should get disclosures from the disclosureStore and populates the component disclosures on the onMounted lifecycle', async () => {
  // Act:先挂载组件,获取实际使用的store实例
  wrapper = mount(BeginDisclosures, {
    global: {
      plugins: [
        createTestingPinia({
          initialState: {
            appStore: {
              //...保留原有内容
            },
            disclosureStore: {
              disclosures: [
                <IDisclosure>{
                  id: 1,
                  name: 'Disclosure 1',
                  displayName: 'Disclosure 1',
                  url: 'http://localhost/api/disclosures/disclosure-1',
                },
                <IDisclosure>{
                  id: 2,
                  name: 'Disclosure 2',
                  displayName: 'Disclosure 2',
                  url: 'http://localhost/api/disclosures/disclosure-2',
                },
                <IDisclosure>{
                  id: 3,
                  name: 'Disclosure 3',
                  displayName: 'Disclosure 3',
                  url: 'http://localhost/api/disclosures/disclosure-3',
                },
              ],
              selectedDisclosures: [],
              selectedDisclosuresReset: false
            }
          },
          createSpy: vi.fn,
          stubActions: false // 关键:不要自动stub所有action,允许自定义mock
        }),
        vuetify
      ]
    }
  });

  // 获取组件内部使用的store实例
  const disclosureStore = useDisclosureStore();
  const appStore = useApplicationStore();

  // 对实际store的方法进行mock
  vi.spyOn(disclosureStore, 'getDisclosuresListAsync').mockResolvedValue(true);
  vi.spyOn(appStore, 'updateShowSpinner').mockImplementation(() => {});

  // 等待异步操作完成
  await wrapper.vm.$nextTick();
  await new Promise(resolve => setTimeout(resolve, 0)); // 确保异步方法执行完毕

  const disclosures = wrapper.vm.disclosures;

  // Assert
  expect(disclosureStore.getDisclosuresListAsync).toHaveBeenCalled();
  expect(disclosures.length).toBe(3);
});

关键要点

  1. stubActions: false必须设置:默认情况下createTestingPinia会把所有action stub成空函数,你的mock会被覆盖,所以要关闭这个选项。
  2. mock组件实际使用的store实例:挂载组件后,通过useDisclosureStore()获取组件内部的store对象,再对它的方法进行mock,这样才能让组件执行时调用到你的mock方法。
  3. 等待异步操作完成:异步方法执行需要时间,除了$nextTick,额外加一个微任务等待确保异步逻辑执行完毕。

内容的提问来源于stack exchange,提问作者Joe King

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 01:48:10