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); });
关键要点
stubActions: false必须设置:默认情况下createTestingPinia会把所有action stub成空函数,你的mock会被覆盖,所以要关闭这个选项。- mock组件实际使用的store实例:挂载组件后,通过
useDisclosureStore()获取组件内部的store对象,再对它的方法进行mock,这样才能让组件执行时调用到你的mock方法。 - 等待异步操作完成:异步方法执行需要时间,除了
$nextTick,额外加一个微任务等待确保异步逻辑执行完毕。
内容的提问来源于stack exchange,提问作者Joe King
相关产品推荐
相关产品推荐

