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

Zustand persist中间件在单元测试中未执行的问题求助

Zustand persist中间件测试不生效问题排查

待测试的Zustand状态管理代码

import { create } from "zustand";
import { persist } from "zustand/middleware";

interface LanguageState {
  language: "en" | "fi";
  setLanguage: (lang: "en" | "fi") => void;
}

const useLanguageStore = create<LanguageState>()(
  persist(
    (set) => ({
      language: "en",
      setLanguage: (lang) => set({ language: lang }),
    }),
    {
      name: "language-storage",
    }
  )
);

export default useLanguageStore;

测试代码(Jest+TypeScript)

import { renderHook, act, waitFor } from "@testing-library/react";
import useLanguageStore from "../../../src/store/useLanguageStore";

const localStorageMock = (() => {
  let store: Record<string, string> = {};

  return {
    getItem: (key: string) => store[key] || null,

    setItem: (key: string, value: string) => {
      store[key] = value;
    },

    removeItem: (key: string) => {
      delete store[key];
    },

    clear: () => {
      store = {};
    },
  };
})();

Object.defineProperty(window, "localStorage", {
  value: localStorageMock,
});

describe("useLanguageStore", () => {
  beforeEach(() => {
    localStorageMock.clear();
    jest.clearAllMocks();
    useLanguageStore.setState({ language: "en" });
  });

  it("1. should have EN as default language and setLanguage when initializing", () => {
    const { result } = renderHook(() => useLanguageStore());

    expect(result.current.language).toBe("en");
    expect(result.current.setLanguage).toBeInstanceOf(Function);
  });

  it("2. should change to FI when setLanguage is triggered", () => {
    const { result } = renderHook(() => useLanguageStore());

    act(() => {
      result.current.setLanguage("fi");
    });

    expect(result.current.language).toBe("fi");
  });

  it("3. should change to EN when setLanguage is triggered", () => {
    const { result } = renderHook(() => useLanguageStore());

    act(() => {
      result.current.setLanguage("en");
    });

    expect(result.current.language).toBe("en");
  });

  it("4. should change to FI when setLanguage is triggered multiple times ", () => {
    const { result } = renderHook(() => useLanguageStore());

    act(() => {
      result.current.setLanguage("en");
    });

    act(() => {
      result.current.setLanguage("fi");
    });

    act(() => {
      result.current.setLanguage("en");
    });

    act(() => {
      result.current.setLanguage("fi");
    });

    expect(result.current.language).toBe("fi");
  });

  it(" should persist language to localStorage", async () => {
    const { result } = renderHook(() => useLanguageStore());

    await act(async () => {
      result.current.setLanguage("fi");
    });

    await waitFor(() => {
      const storedLanguage = localStorageMock.getItem("language-storage");
      expect(storedLanguage).toBeTruthy();

      const parsedLanguage = JSON.parse(storedLanguage!);
      expect(parsedLanguage.state.language).toBe("fi");
    });
  });
});

问题描述

最后一个测试用例中,Zustand的persist中间件未在测试环境下执行,导致模拟localStorage中无数据,但该功能在开发和生产环境正常工作。已尝试刷新微任务、Jest假定时器等方案,问题仍未解决。

解决方案

1. 修正beforeEach的状态重置逻辑

直接调用useLanguageStore.setState会绕过persist中间件的状态监听逻辑,改用store提供的setLanguage方法重置状态,确保触发persist的存储逻辑:

beforeEach(() => {
  localStorageMock.clear();
  jest.clearAllMocks();
  // 通过setLanguage重置,触发persist逻辑
  useLanguageStore.getState().setLanguage("en");
});

或者直接调用persist中间件提供的重置方法,彻底清空存储并重新初始化:

beforeEach(() => {
  localStorageMock.clear();
  jest.clearAllMocks();
  // 清空persist存储并重新hydrate
  useLanguageStore.persist.clearStorage();
  useLanguageStore.persist.rehydrate();
});

2. 简化持久化测试的异步处理

Zustand的persist中间件默认是同步写入localStorage的,无需使用await act和waitFor,直接用同步act包裹状态变更即可:

it("should persist language to localStorage", () => {
  const { result } = renderHook(() => useLanguageStore());

  act(() => {
    result.current.setLanguage("fi");
  });

  const storedLanguage = localStorageMock.getItem("language-storage");
  expect(storedLanguage).toBeTruthy();

  const parsedLanguage = JSON.parse(storedLanguage!);
  expect(parsedLanguage.state.language).toBe("fi");
});

3. 验证localStorage调用(可选)

可以给mock的setItem添加spy,验证persist是否正确调用了localStorage:

// 在beforeEach中添加spy
beforeEach(() => {
  localStorageMock.clear();
  jest.clearAllMocks();
  jest.spyOn(localStorageMock, "setItem");
  useLanguageStore.getState().setLanguage("en");
});

// 在持久化测试中验证
it("should persist language to localStorage", () => {
  const { result } = renderHook(() => useLanguageStore());

  act(() => {
    result.current.setLanguage("fi");
  });

  expect(localStorageMock.setItem).toHaveBeenCalledWith(
    "language-storage",
    expect.stringContaining('"language":"fi"')
  );

  const storedLanguage = localStorageMock.getItem("language-storage");
  const parsedLanguage = JSON.parse(storedLanguage!);
  expect(parsedLanguage.state.language).toBe("fi");
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 03:09:51