如何独立单元测试Pinia Store中的Watcher?
Pinia Store中Watcher单元测试的独立验证方案
问题背景
在测试基于Pinia的布局Store时,需要单独验证监听sidebarMode计算属性的Watcher逻辑——当sidebarMode切换为Static时,自动将sidebarOverlayActive重置为false。由于sidebarMode依赖@vueuse/core的useWindowSize,希望避免依赖window.innerWidth的Mock,单独聚焦Watcher的行为验证。
Store代码如下:
export enum SidebarMode { Static = 'static', Overlay = 'overlay', } export const useLayoutStore = defineStore('layout', () => { const { width: windowWith } = useWindowSize(); const sidebarMode = computed(() => windowWith.value >= 991 ? SidebarMode.Static : SidebarMode.Overlay); const sidebarCollapsed = ref(false); const sidebarOverlayActive = ref(false); function toggleSidebar() { switch (sidebarMode.value) { case SidebarMode.Static: sidebarCollapsed.value = !sidebarCollapsed.value; break; case SidebarMode.Overlay: sidebarOverlayActive.value = !sidebarOverlayActive.value; break; } } watch(sidebarMode, (newMode) => { if (newMode === SidebarMode.Static) sidebarOverlayActive.value = false; }); return { sidebarMode, sidebarCollapsed, sidebarOverlayActive, toggleSidebar }; });
解决方案
方案一:Mock useWindowSize 实现可控的宽度值
通过Mock@vueuse/core的useWindowSize方法,返回一个可手动修改的ref,以此精准控制sidebarMode的变化,从而单独验证Watcher的逻辑。这种方式不依赖真实的window.innerWidth,完全隔离了计算属性的测试场景。
测试代码示例:
import { useLayoutStore } from '@/stores/layout'; import { useWindowSize } from '@vueuse/core'; import { createPinia, setActivePinia } from 'pinia'; import { vi, describe, it, expect, beforeEach, nextTick } from 'vitest'; import { ref } from 'vue'; import { SidebarMode } from '@/stores/layout'; // 全局Mock useWindowSize vi.mock('@vueuse/core', () => ({ useWindowSize: vi.fn(), })); describe('useLayoutStore - sidebarMode watcher', () => { beforeEach(() => { // 重置Pinia实例 setActivePinia(createPinia()); }); it('重置sidebarOverlayActive当sidebarMode切换为Static', async () => { // 创建可控制的宽度ref,初始值对应Overlay模式 const mockWidth = ref(768); (useWindowSize as vi.Mock).mockReturnValue({ width: mockWidth }); const store = useLayoutStore(); // 先将overlay状态设为true store.sidebarOverlayActive = true; expect(store.sidebarOverlayActive).toBe(true); // 修改宽度,触发sidebarMode切换为Static mockWidth.value = 1024; // 等待Watcher异步执行 await nextTick(); expect(store.sidebarOverlayActive).toBe(false); }); it('sidebarMode切换为Overlay时不修改sidebarOverlayActive', async () => { const mockWidth = ref(1024); // 初始为Static模式 (useWindowSize as vi.Mock).mockReturnValue({ width: mockWidth }); const store = useLayoutStore(); store.sidebarOverlayActive = true; expect(store.sidebarOverlayActive).toBe(true); // 修改宽度切换为Overlay模式 mockWidth.value = 768; await nextTick(); expect(store.sidebarOverlayActive).toBe(true); }); });
方案二:直接替换Store中的sidebarMode为可控Ref
如果不想MockuseWindowSize,可以通过重定义Store的sidebarMode属性,用一个可手动修改的ref替换原计算属性,直接触发Watcher的逻辑。
测试代码示例:
import { useLayoutStore } from '@/stores/layout'; import { createPinia, setActivePinia } from 'pinia'; import { vi, describe, it, expect, beforeEach, nextTick } from 'vitest'; import { ref } from 'vue'; import { SidebarMode } from '@/stores/layout'; describe('useLayoutStore - sidebarMode watcher', () => { beforeEach(() => { setActivePinia(createPinia()); }); it('手动切换sidebarMode为Static时触发状态重置', async () => { const store = useLayoutStore(); // 创建可控制的sidebarMode ref const mockSidebarMode = ref(SidebarMode.Overlay); // 替换原计算属性为自定义ref的getter Object.defineProperty(store, 'sidebarMode', { get: () => mockSidebarMode.value, configurable: true }); store.sidebarOverlayActive = true; expect(store.sidebarOverlayActive).toBe(true); // 手动修改sidebarMode值 mockSidebarMode.value = SidebarMode.Static; await nextTick(); expect(store.sidebarOverlayActive).toBe(false); }); });
方案说明
- 方案一更符合单元测试的隔离原则,通过Mock依赖实现对Watcher逻辑的精准测试,同时与
sidebarMode的计算逻辑测试完全解耦。 - 方案二更直接,但需要修改Store的属性定义,适合快速验证场景,不推荐作为长期测试方案。
内容的提问来源于stack exchange,提问作者Raphael
相关产品推荐
相关产品推荐

