V

Vue + Pinia 单元测试

作者:鹿Sir开发工具v1

为 Vue 3 + TypeScript + Vitest + Pinia 代码库编写和评审单元测试,覆盖组件、组合式函数与 store 三类对象,规范 createTestingPinia 的使用姿势与 Vue Test Utils 模式,坚持黑盒断言、避免耦合实现细节。当用户需要编写或评审组件/store 单元测试、搭建 Vitest 测试、mock Pinia 时触发。触发词:单元测试、Vue 测试、Vitest、Pinia 测试、测试评审。

下载量
343
点赞
83
价格
免费

技能文档

---
name: github-unit-test-vue-pinia
title: Vue + Pinia 单元测试
category: 开发工具
description: 为 Vue 3 + TypeScript + Vitest + Pinia 代码库编写和评审单元测试,覆盖组件、组合式函数与 store 三类对象,规范 createTestingPinia 的使用姿势与 Vue Test Utils 模式,坚持黑盒断言、避免耦合实现细节。当用户需要编写或评审组件/store 单元测试、搭建 Vitest 测试、mock Pinia 时触发。触发词:单元测试、Vue 测试、Vitest、Pinia 测试、测试评审。
---

# Vue + Pinia 单元测试

为 Vue 组件、组合式函数与 Pinia store 创建或评审单元测试,保持测试短小、确定、行为优先。

## 技能工作流

### 步骤1:确定行为边界

先明确要测试的行为边界属于哪一类:组件 UI 行为、组合式函数行为,还是 store 行为。

### 步骤2:选择最窄的测试方式

选择能够证明该行为的最小测试样式,不引入多余的挂载深度或真实依赖。

### 步骤3:搭建 Pinia 测试环境

用覆盖场景所需的最小能力项配置 Pinia(详见下方「Pinia 测试要点」)。

### 步骤4:通过公开输入驱动测试

通过 props、表单更新、按钮点击、子组件事件、store API 等公开输入触发行为。

### 步骤5:断言可观察输出

优先断言可观察的输出与副作用(渲染文本、触发的事件、回调调用、store 状态变化),确认之后再考虑实例级断言。

### 步骤6:命名与收尾

用清晰的行为化名称命名测试,并指出尚存的覆盖缺口。

## 核心规则

- 每个测试只验证一个行为。
- 优先断言可观察的输入/输出行为(渲染文本、事件、回调、store 状态)。
- 避免与实现细节耦合的断言。
- 仅在没有合理的 DOM/props/事件/store 级断言时才访问 `wrapper.vm`,并视为例外。
- 在 `beforeEach()` 中显式初始化,每个测试重置 mock。
- 标准 Pinia 测试写法以 `references/pinia-patterns.md` 为本地事实来源。

## Pinia 测试要点

优先使用 `references/pinia-patterns.md`,本地样例覆盖不了的场景再查 Pinia 官方测试手册。

### 组件测试默认写法

挂载时把 `createTestingPinia` 作为全局插件注入,默认使用 `createSpy: vi.fn`,保持一致且便于断言 action 调用:

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

默认情况下 action 被替身化并附加侦听。测试只需要验证某个 action 被调用(或未被调用)时,保持 `stubActions: true`(默认值)。

### 可接受的最小 Pinia 配置

以下写法同样正确,评审时不应判为错误:

- 测试不断言 action 侦听行为时,可用 `createTestingPinia({})`。
- 只需要预置状态或替身化 action、不检查生成的 spy 时,可用不带 `createSpy` 的 `createTestingPinia({ initialState: ... })` 或 `createTestingPinia({ stubActions: ... })`。
- 不挂载组件、只测 store/组合式函数,且需要 mock 或预置依赖 store 时,可用 `setActivePinia(createTestingPinia(...))`。

只有当 action 侦听断言本身就是测试意图时才使用 `createSpy: vi.fn`。

### 仅在必要时执行真实 action

只有测试必须验证 action 的真实行为与副作用时才使用 `stubActions: false`;简单的「是否被调用」断言不要打开它。

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

### 用 `initialState` 预置 store 状态

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

### 通过 `createTestingPinia` 注入插件

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

### 边界场景的 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;
```

### 纯 store 单元测试

目标是验证 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 要点

遵循 Vue Test Utils 官方指南:<https://test-utils.vuejs.org/guide/>

- 聚焦单元测试默认浅挂载(shallow)。
- 仅当被测对象是集成行为时才挂载完整组件树。
- 通过 props、拟真交互、事件触发来驱动行为。
- 子组件桩事件优先用 `findComponent(...).vm.$emit(...)`,不触碰父组件内部。
- 仅当更新是异步时才使用 `nextTick`。
- 用 `wrapper.emitted(...)` 断言事件与载荷。
- 只有当 DOM/事件/props/store 级断言都无法表达该行为时才访问 `wrapper.vm`,并保持断言范围最小。

## 关键测试片段

触发并断言事件载荷:

```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");
```

## 测试编写流程

1. 确定要测试的行为边界。
2. 构造最小夹具数据(只包含该行为需要的字段)。
3. 配置 Pinia 与必要的测试替身。
4. 通过公开输入触发行为。
5. 断言公开输出与副作用。
6. 把测试名称重构为描述行为而非实现。

## 约束与安全

- 不测试私有/内部实现细节。
- 不对动态 UI 行为滥用快照。
- 大对象中只有一个行为重要时,不断言每一个字段。
- 假数据保持确定性,避免随机值。
- 属于上文「可接受的最小 Pinia 配置」的写法,不得判为错误。
- 除非被测行为确实需要,不把能用的测试重写为更深的挂载或真实 action。
- 评审时明确指出:缺失的覆盖、脆弱的选择器、与实现耦合的断言。

## 输出约定

- 「创建/更新」场景:返回完成的测试代码,并附一句说明所选的 Pinia 策略。
- 「评审」场景:先给出具体发现,再列缺失覆盖与脆弱性风险。
- 最优选择不明确时,说明驱动当前测试配置的假设。

## 参考资料

- `references/pinia-patterns.md`
- Pinia 测试手册:<https://pinia.vuejs.org/zh/cookbook/testing.html>
- Vue Test Utils 指南:<https://test-utils.vuejs.org/guide/>

使用说明

# Vue + Pinia 单元测试

为 Vue 3 + TypeScript + Vitest + Pinia 代码库编写和评审单元测试,坚持黑盒断言与行为优先,规范 createTestingPinia 的使用姿势。

## 使用

```text
为这个组件写单元测试:它点击提交按钮后触发 submit 事件并携带表单值,
其中用到了 Pinia store 的 saveData action。
```

输出为可直接落盘的测试代码,附所选 Pinia 策略说明;评审场景则输出具体问题清单与覆盖缺口。

## 工作原理

1. 确定行为边界(组件 / 组合式函数 / store),选择能证明行为的最窄测试样式
2. 按需配置 Pinia:默认 `createTestingPinia({ createSpy: vi.fn })`,仅在必要时执行真实 action 或预置 initialState
3. 通过 props、点击、表单等公开输入驱动行为,断言渲染输出、事件与 store 状态,禁止耦合实现细节

如何安装此技能?

访问技能市场,点击「安装」按钮,按提示将技能包放入 AI 编程助手的 skills 目录即可。

浏览技能市场

支持平台:Qoder · QoderWork · Claude · Codex 等 AI 编程助手