如何在测试间重置Vue Composable的全局状态?
问题:测试全局状态Composable时的状态重置难题
我正在使用一个带全局状态的Vue Composable:
const state = ref([]); export function useFoo() { const loadFoos = async () => { const data = await fetch(/* API地址 */) state.value = await data.json(); } return { state: readonly(state), // 外部只读 loadFoos } }
想用Jest测试这个Composable,写了两个测试用例:
- 验证是否调用了正确的API
- 验证状态是否正确填充数据
但第二个测试用例总是失败——因为全局状态是单例模式,第一个测试运行后的数据会残留下来,导致初始状态不是预期的空数组:
describe("useFoos", () => { const data = { data: "data" }; beforeEach(() => { jest.resetModules(); fetch.mockResolvedValue([data]); }); it("调用API", async () => { const { state, loadFoos } = useFoos(); await loadFoos(); expect(fetch).toHaveBeenCalledTimes(1); }); it("存储数据", async () => { const { state, loadFoos } = useFoos(); // 原代码里的useRoutes是笔误 // 测试失败:state.value不是[],而是第一个测试留下的[data] expect(state.value).toEqual([]); await loadFoos(); expect(state.value).toEqual([data]); }); });
我不想在生产代码里加reset函数,之前试过jest.isolateModules但没效果,请问有没有办法在每个测试里重置state.value?
解决方案
方法1:正确使用jest.isolateModules隔离模块(最推荐)
你之前用jest.isolateModules无效,是因为没有在隔离块内部导入Composable。正确的做法是每个测试都在隔离模块环境中重新导入useFoo,这样模块代码会重新执行,创建全新的state实例,自然重置状态。
修改后的测试代码:
describe("useFoos", () => { const testData = { data: "data" }; beforeEach(() => { // 清空fetch的mock调用记录 fetch.mockClear(); // 正确mock fetch的返回(匹配原代码中data.json()的调用逻辑) fetch.mockResolvedValue({ json: async () => [testData] }); }); it("调用正确的API", async () => { let useFoo; // 隔离模块,重新执行模块代码,生成新的state jest.isolateModules(() => { useFoo = require('./path/to/useFoo').useFoo; }); const { loadFoos } = useFoo(); await loadFoos(); expect(fetch).toHaveBeenCalledTimes(1); }); it("正确填充状态数据", async () => { let useFoo; jest.isolateModules(() => { useFoo = require('./path/to/useFoo').useFoo; }); const { state, loadFoos } = useFoo(); // 初始状态为空数组 expect(state.value).toEqual([]); await loadFoos(); expect(state.value).toEqual([testData]); }); });
方法2:动态Mock模块实现
通过jest.mock在每个测试前动态生成模块的新实例,确保每次测试都有独立的状态,完全不依赖生产代码的单例:
describe("useFoos", () => { const testData = { data: "data" }; beforeEach(() => { fetch.mockClear(); fetch.mockResolvedValue({ json: async () => [testData] }); // 每次测试都mock模块,创建全新的state和useFoo实现 jest.mock('./path/to/useFoo', () => { const { ref, readonly } = require('vue'); return { useFoo: () => { const state = ref([]); const loadFoos = async () => { const res = await fetch('你的API地址'); state.value = await res.json(); }; return { state: readonly(state), loadFoos }; } }; }); // 清除模块缓存,确保每次都用新的mock jest.resetModules(); }); it("调用正确的API", async () => { const { useFoo } = require('./path/to/useFoo'); const { loadFoos } = useFoo(); await loadFoos(); expect(fetch).toHaveBeenCalledTimes(1); }); it("正确填充状态数据", async () => { const { useFoo } = require('./path/to/useFoo'); const { state, loadFoos } = useFoo(); expect(state.value).toEqual([]); await loadFoos(); expect(state.value).toEqual([testData]); }); });
方法3:直接重置私有状态(应急方案)
如果上面两种方法都不适用,可以通过Node.js的模块特性直接访问并重置模块内的私有state变量(注意:依赖模块的编译方式,可能会因打包工具变化失效):
import * as useFooModule from './path/to/useFoo'; import { toRaw } from 'vue'; describe("useFoos", () => { const testData = { data: "data" }; beforeEach(() => { fetch.mockClear(); fetch.mockResolvedValue({ json: async () => [testData] }); // 获取模块内的私有state变量 const [composable] = Object.values(useFooModule); const internalState = composable.state; // 重置状态为初始空数组 toRaw(internalState).value = []; }); it("调用正确的API", async () => { const { loadFoos } = useFooModule.useFoo(); await loadFoos(); expect(fetch).toHaveBeenCalledTimes(1); }); it("正确填充状态数据", async () => { const { state, loadFoos } = useFooModule.useFoo(); expect(state.value).toEqual([]); await loadFoos(); expect(state.value).toEqual([testData]); }); });
内容的提问来源于stack exchange,提问作者Theiaz
相关产品推荐
相关产品推荐

