unit-test-vue-pinia
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseunit-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
工作流程
- Identify the behavior boundary first: component UI behavior, composable behavior, or store behavior.
- Choose the narrowest test style that can prove that behavior.
- Set up Pinia with the least powerful option that still covers the scenario.
- Drive the test through public inputs such as props, form updates, button clicks, emitted child events, and store APIs.
- Assert observable outputs and side effects before considering any instance-level assertion.
- Return or review tests with clear behavior-oriented names and note any remaining coverage gaps.
- 首先明确行为边界:组件UI行为、组合式函数行为或Store行为。
- 选择能够验证该行为的最精简测试风格。
- 使用足以覆盖场景的最简配置来设置Pinia。
- 通过公开输入触发测试,如Props、表单更新、按钮点击、子组件触发的事件以及Store API。
- 在考虑任何实例级断言之前,先断言可观察的输出和副作用。
- 返回或评审具有清晰行为导向命名的测试,并标注任何剩余的覆盖缺口。
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 only in exceptional cases when there is no reasonable DOM, prop, emit, or store-level assertion.
wrapper.vm - Prefer explicit setup in and reset mocks every test.
beforeEach() - Use checked-in reference material in as the local source of truth for standard Pinia test setups.
references/pinia-patterns.md
- 每个测试仅验证一个行为。
- 优先断言可观察的输入/输出行为(渲染文本、触发的事件、回调调用、Store状态变化)。
- 避免与实现耦合的断言。
- 仅在无法通过DOM、Props、事件触发或Store级别的合理断言时,才在特殊情况下使用。
wrapper.vm - 优先在中进行显式设置,并在每次测试后重置模拟。
beforeEach() - 以中已提交的参考资料作为Pinia标准测试配置的本地权威来源。
references/pinia-patterns.md
Pinia Testing Approach
Pinia测试方法
Use first, then fall back to Pinia's testing cookbook when the checked-in examples do not cover the case.
references/pinia-patterns.md优先使用,当已提交的示例无法覆盖场景时,再参考Pinia的测试手册。
references/pinia-patterns.mdDefault pattern for component tests
组件测试的默认模式
Use as a global plugin while mounting.
Prefer as the default for consistency and easier action-spy assertions.
createTestingPiniacreateSpy: vi.fnts
const wrapper = mount(ComponentUnderTest, {
global: {
plugins: [
createTestingPinia({
createSpy: vi.fn,
}),
],
},
});By default, actions are stubbed and spied.
Use (default) when the test only needs to verify whether an action was called (or not called).
stubActions: true在挂载组件时,将作为全局插件使用。
为了保持一致性并简化action间谍断言,默认优先使用。
createTestingPiniacreateSpy: vi.fnts
const wrapper = mount(ComponentUnderTest, {
global: {
plugins: [
createTestingPinia({
createSpy: vi.fn,
}),
],
},
});默认情况下,actions会被存根并设置间谍。
当测试仅需要验证action是否被调用(或未被调用)时,使用默认的。
stubActions: trueAccepted minimal Pinia setups
可接受的极简Pinia配置
The following are also valid and should not be flagged as incorrect:
- when the test does not assert Pinia action spy behavior.
createTestingPinia({}) - or
createTestingPinia({ initialState: ... })withoutcreateTestingPinia({ stubActions: ... }), when the test only needs state seeding or action stubbing behavior and does not inspect generated spies.createSpy - in store/composable-focused tests (without mounting a component) when mocking/seeding dependent stores is needed.
setActivePinia(createTestingPinia(...))
Use when action spy assertions are part of the test intent.
createSpy: vi.fn以下配置同样有效,不应被标记为错误:
- 当测试不需要断言Pinia action间谍行为时,使用。
createTestingPinia({}) - 当测试仅需要状态预填充或action存根行为,且无需检查生成的间谍时,使用或
createTestingPinia({ initialState: ... })(不设置createTestingPinia({ stubActions: ... }))。createSpy - 在以Store/组合式函数为核心的测试中(无需挂载组件),当需要模拟/预填充依赖Store时,使用。
setActivePinia(createTestingPinia(...))
当测试需要断言action间谍时,使用。
createSpy: vi.fnExecute real actions only when needed
仅在必要时执行真实actions
Use 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.
stubActions: falsets
const wrapper = mount(ComponentUnderTest, {
global: {
plugins: [
createTestingPinia({
createSpy: vi.fn,
stubActions: false,
}),
],
},
});仅当测试必须验证action的真实行为和副作用时,才使用。对于简单的“是否被调用”断言,不要默认开启此配置。
stubActions: falsets
const wrapper = mount(ComponentUnderTest, {
global: {
plugins: [
createTestingPinia({
createSpy: vi.fn,
stubActions: false,
}),
],
},
});Seed store state with initialState
initialState使用initialState
预填充Store状态
initialStatets
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通过createTestingPinia
添加Pinia插件
createTestingPiniats
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 when the goal is to validate store state transitions and action behavior without component rendering. Use only when you need stubbed dependent stores, seeded test doubles, or action spies.
createPinia()createTestingPinia()ts
beforeEach(() => {
setActivePinia(createPinia());
});
it("increments", () => {
const counter = useCounterStore();
counter.increment();
expect(counter.n).toBe(1);
});当目标是在不渲染组件的情况下验证Store状态转换和action行为时,优先使用进行纯Store测试。仅当需要存根依赖Store、预填充测试替身或action间谍时,才使用。
createPinia()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 for child stub events instead of touching parent internals.
findComponent(...).vm.$emit(...) - Use only when updates are async.
nextTick - Assert emitted events and payloads with .
wrapper.emitted(...) - Access 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.
wrapper.vm
遵循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
测试编写流程
- Identify the behavior boundary to test.
- Build minimal fixture data (only fields needed by that behavior).
- Configure Pinia and required test doubles.
- Trigger behavior through public inputs.
- Assert public outputs and side effects.
- Refactor test names to describe behavior, not implementation.
- 确定要测试的行为边界。
- 构建极简的测试数据(仅包含该行为所需的字段)。
- 配置Pinia和所需的测试替身。
- 通过公开输入触发行为。
- 断言公开输出和副作用。
- 重构测试名称以描述行为,而非实现细节。
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 or
create, return the finished test code plus a short note describing the selected Pinia strategy.update - For , return concrete findings first, then missing coverage or brittleness risks.
review - When the safest choice is ambiguous, state the assumption that drove the chosen test setup.
- 对于「创建」或「更新」请求,返回完成的测试代码,并附上简短说明,描述所选的Pinia策略。
- 对于「评审」请求,先返回具体的发现,再说明缺失的覆盖范围或脆弱性风险。
- 当最安全的选择不明确时,说明驱动所选测试配置的假设。
References
参考资料
references/pinia-patterns.md- Pinia testing cookbook: https://pinia.vuejs.org/cookbook/testing.html
- Vue Test Utils guide: https://test-utils.vuejs.org/guide/
references/pinia-patterns.md- Pinia测试手册:https://pinia.vuejs.org/cookbook/testing.html
- Vue Test Utils指南:https://test-utils.vuejs.org/guide/