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

如何在测试间重置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,写了两个测试用例:

  1. 验证是否调用了正确的API
  2. 验证状态是否正确填充数据

但第二个测试用例总是失败——因为全局状态是单例模式,第一个测试运行后的数据会残留下来,导致初始状态不是预期的空数组:

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 10:15:06