unit-test-vue-pinia

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

unit-test-vue-pinia

Vue + Pinia单元测试

Use this skill to create or review unit tests for Vue components, composables, and Pinia stores. Keep tests small, deterministic, and behavior-first.
使用此技能为Vue组件、组合式函数和Pinia Store创建或评审单元测试。保持测试简洁、可预测,且以行为为核心。

Workflow

工作流程

  1. Identify the behavior boundary first: component UI behavior, composable behavior, or store behavior.
  2. Choose the narrowest test style that can prove that behavior.
  3. Set up Pinia with the least powerful option that still covers the scenario.
  4. Drive the test through public inputs such as props, form updates, button clicks, emitted child events, and store APIs.
  5. Assert observable outputs and side effects before considering any instance-level assertion.
  6. Return or review tests with clear behavior-oriented names and note any remaining coverage gaps.
  1. 首先明确行为边界:组件UI行为、组合式函数行为或Store行为。
  2. 选择能够验证该行为的最精简测试风格。
  3. 使用足以覆盖场景的最简配置来设置Pinia。
  4. 通过公开输入触发测试,如Props、表单更新、按钮点击、子组件触发的事件以及Store API。
  5. 在考虑任何实例级断言之前,先断言可观察的输出和副作用。
  6. 返回或评审具有清晰行为导向命名的测试,并标注任何剩余的覆盖缺口。

Core Rules

核心规则

  • Test one behavior per test.
  • Assert observable input/output behavior first (rendered text, emitted events, callback calls, store state changes).
  • Avoid implementation-coupled assertions.
  • Access
    wrapper.vm
    only in exceptional cases when there is no reasonable DOM, prop, emit, or store-level assertion.
  • Prefer explicit setup in
    beforeEach()
    and reset mocks every test.
  • Use checked-in reference material in
    references/pinia-patterns.md
    as the local source of truth for standard Pinia test setups.
  • 每个测试仅验证一个行为。
  • 优先断言可观察的输入/输出行为(渲染文本、触发的事件、回调调用、Store状态变化)。
  • 避免与实现耦合的断言。
  • 仅在无法通过DOM、Props、事件触发或Store级别的合理断言时,才在特殊情况下使用
    wrapper.vm
  • 优先在
    beforeEach()
    中进行显式设置,并在每次测试后重置模拟。
  • references/pinia-patterns.md
    中已提交的参考资料作为Pinia标准测试配置的本地权威来源。

Pinia Testing Approach

Pinia测试方法

Use
references/pinia-patterns.md
first, then fall back to Pinia's testing cookbook when the checked-in examples do not cover the case.
优先使用
references/pinia-patterns.md
,当已提交的示例无法覆盖场景时,再参考Pinia的测试手册。

Default pattern for component tests

组件测试的默认模式

Use
createTestingPinia
as a global plugin while mounting. Prefer
createSpy: vi.fn
as the default for consistency and easier action-spy assertions.
ts
const wrapper = mount(ComponentUnderTest, {
	global: {
		plugins: [
			createTestingPinia({
				createSpy: vi.fn,
			}),
		],
	},
});
By default, actions are stubbed and spied. Use
stubActions: true
(default) when the test only needs to verify whether an action was called (or not called).
在挂载组件时,将
createTestingPinia
作为全局插件使用。 为了保持一致性并简化action间谍断言,默认优先使用
createSpy: vi.fn
ts
const wrapper = mount(ComponentUnderTest, {
	global: {
		plugins: [
			createTestingPinia({
				createSpy: vi.fn,
			}),
		],
	},
});
默认情况下,actions会被存根并设置间谍。 当测试仅需要验证action是否被调用(或未被调用)时,使用默认的
stubActions: true

Accepted minimal Pinia setups

可接受的极简Pinia配置

The following are also valid and should not be flagged as incorrect:
  • createTestingPinia({})
    when the test does not assert Pinia action spy behavior.
  • createTestingPinia({ initialState: ... })
    or
    createTestingPinia({ stubActions: ... })
    without
    createSpy
    , when the test only needs state seeding or action stubbing behavior and does not inspect generated spies.
  • setActivePinia(createTestingPinia(...))
    in store/composable-focused tests (without mounting a component) when mocking/seeding dependent stores is needed.
Use
createSpy: vi.fn
when action spy assertions are part of the test intent.
以下配置同样有效,不应被标记为错误:
  • 当测试不需要断言Pinia action间谍行为时,使用
    createTestingPinia({})
  • 当测试仅需要状态预填充或action存根行为,且无需检查生成的间谍时,使用
    createTestingPinia({ initialState: ... })
    createTestingPinia({ stubActions: ... })
    (不设置
    createSpy
    )。
  • 在以Store/组合式函数为核心的测试中(无需挂载组件),当需要模拟/预填充依赖Store时,使用
    setActivePinia(createTestingPinia(...))
当测试需要断言action间谍时,使用
createSpy: vi.fn

Execute real actions only when needed

仅在必要时执行真实actions

Use
stubActions: false
only when the test must validate the action's real behavior and side effects. Do not switch it on by default for simple "was called" assertions.
ts
const wrapper = mount(ComponentUnderTest, {
	global: {
		plugins: [
			createTestingPinia({
				createSpy: vi.fn,
				stubActions: false,
			}),
		],
	},
});
仅当测试必须验证action的真实行为和副作用时,才使用
stubActions: false
。对于简单的“是否被调用”断言,不要默认开启此配置。
ts
const wrapper = mount(ComponentUnderTest, {
	global: {
		plugins: [
			createTestingPinia({
				createSpy: vi.fn,
				stubActions: false,
			}),
		],
	},
});

Seed store state with
initialState

使用
initialState
预填充Store状态

ts
const wrapper = mount(ComponentUnderTest, {
	global: {
		plugins: [
			createTestingPinia({
				createSpy: vi.fn,
				initialState: {
					counter: { n: 20 },
					user: { name: "Leia Organa" },
				},
			}),
		],
	},
});
ts
const wrapper = mount(ComponentUnderTest, {
	global: {
		plugins: [
			createTestingPinia({
				createSpy: vi.fn,
				initialState: {
					counter: { n: 20 },
					user: { name: "Leia Organa" },
				},
			}),
		],
	},
});

Add Pinia plugins through
createTestingPinia

通过
createTestingPinia
添加Pinia插件

ts
const wrapper = mount(ComponentUnderTest, {
	global: {
		plugins: [
			createTestingPinia({
				createSpy: vi.fn,
				plugins: [myPiniaPlugin],
			}),
		],
	},
});
ts
const wrapper = mount(ComponentUnderTest, {
	global: {
		plugins: [
			createTestingPinia({
				createSpy: vi.fn,
				plugins: [myPiniaPlugin],
			}),
		],
	},
});

Getter override pattern for edge cases

边缘场景下的Getter覆盖模式

ts
const pinia = createTestingPinia({ createSpy: vi.fn });
const store = useCounterStore(pinia);

store.double = 999;
// @ts-expect-error test-only reset of overridden getter
store.double = undefined;
ts
const pinia = createTestingPinia({ createSpy: vi.fn });
const store = useCounterStore(pinia);

store.double = 999;
// @ts-expect-error test-only reset of overridden getter
store.double = undefined;

Pure store unit tests

纯Store单元测试

Prefer pure store tests with
createPinia()
when the goal is to validate store state transitions and action behavior without component rendering. Use
createTestingPinia()
only when you need stubbed dependent stores, seeded test doubles, or action spies.
ts
beforeEach(() => {
	setActivePinia(createPinia());
});

it("increments", () => {
	const counter = useCounterStore();
	counter.increment();
	expect(counter.n).toBe(1);
});
当目标是在不渲染组件的情况下验证Store状态转换和action行为时,优先使用
createPinia()
进行纯Store测试。仅当需要存根依赖Store、预填充测试替身或action间谍时,才使用
createTestingPinia()
ts
beforeEach(() => {
	setActivePinia(createPinia());
});

it("increments", () => {
	const counter = useCounterStore();
	counter.increment();
	expect(counter.n).toBe(1);
});

Vue Test Utils Approach

Vue Test Utils方法

Follow Vue Test Utils guidance: https://test-utils.vuejs.org/guide/
  • Mount shallow by default for focused unit tests.
  • Mount full component trees only when integration behavior is the subject.
  • Drive behavior through props, user-like interactions, and emitted events.
  • Prefer
    findComponent(...).vm.$emit(...)
    for child stub events instead of touching parent internals.
  • Use
    nextTick
    only when updates are async.
  • Assert emitted events and payloads with
    wrapper.emitted(...)
    .
  • Access
    wrapper.vm
    only when no DOM assertion, emitted event assertion, prop assertion, or store-level assertion can express the behavior. Treat it as an exception and keep the assertion narrowly scoped.
遵循Vue Test Utils指南:https://test-utils.vuejs.org/guide/
  • 默认使用浅挂载进行聚焦的单元测试。
  • 仅当以集成行为为测试主题时,才挂载完整组件树。
  • 通过Props、类用户交互和触发的事件来驱动行为。
  • 对于子组件存根事件,优先使用
    findComponent(...).vm.$emit(...)
    ,而非直接操作父组件内部。
  • 仅当更新为异步时才使用
    nextTick
  • 使用
    wrapper.emitted(...)
    断言触发的事件和负载。
  • 仅当无法通过DOM断言、触发事件断言、Props断言或Store级断言来表达行为时,才使用
    wrapper.vm
    。将其视为例外情况,并保持断言范围尽可能狭窄。

Key Testing Snippets

关键测试代码片段

Emit and assert payload:
ts
await wrapper.find("button").trigger("click");
expect(wrapper.emitted("submit")?.[0]?.[0]).toBe("Mango Mission");
Update input and assert output:
ts
await wrapper.find("input").setValue("Agent Violet");
await wrapper.find("form").trigger("submit");
expect(wrapper.emitted("save")?.[0]?.[0]).toBe("Agent Violet");
触发事件并断言负载:
ts
await wrapper.find("button").trigger("click");
expect(wrapper.emitted("submit")?.[0]?.[0]).toBe("Mango Mission");
更新输入并断言输出:
ts
await wrapper.find("input").setValue("Agent Violet");
await wrapper.find("form").trigger("submit");
expect(wrapper.emitted("save")?.[0]?.[0]).toBe("Agent Violet");

Test Writing Workflow

测试编写流程

  1. Identify the behavior boundary to test.
  2. Build minimal fixture data (only fields needed by that behavior).
  3. Configure Pinia and required test doubles.
  4. Trigger behavior through public inputs.
  5. Assert public outputs and side effects.
  6. Refactor test names to describe behavior, not implementation.
  1. 确定要测试的行为边界。
  2. 构建极简的测试数据(仅包含该行为所需的字段)。
  3. 配置Pinia和所需的测试替身。
  4. 通过公开输入触发行为。
  5. 断言公开输出和副作用。
  6. 重构测试名称以描述行为,而非实现细节。

Constraints and Safety

约束与注意事项

  • Do not test private/internal implementation details.
  • Do not overuse snapshots for dynamic UI behavior.
  • Do not assert every field in large objects if only one behavior matters.
  • Keep fake data deterministic; avoid random values.
  • Do not claim a Pinia setup is wrong when it is one of the accepted minimal setups above.
  • Do not rewrite working tests toward deeper mounting or real actions unless the behavior under test requires that extra surface area.
  • Flag missing test coverage, brittle selectors, and implementation-coupled assertions explicitly during review.
  • 不要测试私有/内部实现细节。
  • 不要过度使用快照测试动态UI行为。
  • 如果仅涉及一个行为,不要断言大型对象中的每个字段。
  • 保持假数据可预测;避免随机值。
  • 当Pinia配置属于上述可接受的极简配置之一时,不要判定其为错误。
  • 除非测试的行为需要额外的覆盖范围,否则不要将可正常运行的测试重构成更深层次的挂载或使用真实actions。
  • 在评审过程中,明确标记缺失的测试覆盖、脆弱的选择器以及与实现耦合的断言。

Output Contract

输出约定

  • For
    create
    or
    update
    , return the finished test code plus a short note describing the selected Pinia strategy.
  • For
    review
    , return concrete findings first, then missing coverage or brittleness risks.
  • When the safest choice is ambiguous, state the assumption that drove the chosen test setup.
  • 对于「创建」或「更新」请求,返回完成的测试代码,并附上简短说明,描述所选的Pinia策略。
  • 对于「评审」请求,先返回具体的发现,再说明缺失的覆盖范围或脆弱性风险。
  • 当最安全的选择不明确时,说明驱动所选测试配置的假设。

References

参考资料