mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 09:37:28 +00:00
189 lines
4.2 KiB
Text
189 lines
4.2 KiB
Text
---
|
|
description: 桌面端测试
|
|
globs:
|
|
alwaysApply: false
|
|
---
|
|
|
|
# 桌面端控制器单元测试指南
|
|
|
|
## 测试框架与目录结构
|
|
|
|
LobeChat 桌面端使用 Vitest 作为测试框架。控制器的单元测试应放置在对应控制器文件同级的 `__tests__` 目录下,并以原控制器文件名加 `.test.ts` 作为文件名。
|
|
|
|
```plaintext
|
|
apps/desktop/src/main/controllers/
|
|
├── __tests__/
|
|
│ ├── index.test.ts
|
|
│ ├── MenuCtr.test.ts
|
|
│ └── ...
|
|
├── McpCtr.ts
|
|
├── MenuCtr.ts
|
|
└── ...
|
|
```
|
|
|
|
## 测试文件基本结构
|
|
|
|
```typescript
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import type { App } from '@/core/App';
|
|
|
|
import YourController from '../YourControllerName';
|
|
|
|
// 模拟依赖
|
|
vi.mock('依赖模块', () => ({
|
|
依赖函数: vi.fn(),
|
|
}));
|
|
|
|
// 模拟 App 实例
|
|
const mockApp = {
|
|
// 按需模拟必要的 App 属性和方法
|
|
} as unknown as App;
|
|
|
|
describe('YourController', () => {
|
|
let controller: YourController;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
controller = new YourController(mockApp);
|
|
});
|
|
|
|
describe('方法名', () => {
|
|
it('测试场景描述', async () => {
|
|
// 准备测试数据
|
|
|
|
// 执行被测方法
|
|
const result = await controller.方法名(参数);
|
|
|
|
// 验证结果
|
|
expect(result).toMatchObject(预期结果);
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
## 模拟外部依赖
|
|
|
|
### 模拟模块函数
|
|
|
|
```typescript
|
|
const mockFunction = vi.fn();
|
|
|
|
vi.mock('module-name', () => ({
|
|
functionName: mockFunction,
|
|
}));
|
|
```
|
|
|
|
### 模拟 Node.js 核心模块
|
|
|
|
例如模拟 `child_process.exec` 和 `util.promisify`:
|
|
|
|
```typescript
|
|
// 存储模拟的 exec 实现
|
|
const mockExecImpl = vi.fn();
|
|
|
|
// 模拟 child_process.exec
|
|
vi.mock('child_process', () => ({
|
|
exec: vi.fn((cmd, callback) => {
|
|
return mockExecImpl(cmd, callback);
|
|
}),
|
|
}));
|
|
|
|
// 模拟 util.promisify
|
|
vi.mock('util', () => ({
|
|
promisify: vi.fn((fn) => {
|
|
return async (cmd: string) => {
|
|
return new Promise((resolve, reject) => {
|
|
mockExecImpl(cmd, (error: Error | null, result: any) => {
|
|
if (error) reject(error);
|
|
else resolve(result);
|
|
});
|
|
});
|
|
};
|
|
}),
|
|
}));
|
|
```
|
|
|
|
## 编写有效的测试用例
|
|
|
|
### 测试分类
|
|
|
|
将测试用例分为不同类别,每个类别测试一个特定场景:
|
|
|
|
```typescript
|
|
// 成功场景
|
|
it('应该成功完成操作', async () => {});
|
|
|
|
// 边界条件
|
|
it('应该处理边界情况', async () => {});
|
|
|
|
// 错误处理
|
|
it('应该优雅地处理错误', async () => {});
|
|
```
|
|
|
|
### 设置测试数据
|
|
|
|
```typescript
|
|
// 模拟返回值
|
|
mockExecImpl.mockImplementation((cmd: string, callback: any) => {
|
|
if (cmd === '命令') {
|
|
callback(null, { stdout: '成功输出' });
|
|
} else {
|
|
callback(new Error('错误信息'), null);
|
|
}
|
|
});
|
|
```
|
|
|
|
### 断言
|
|
|
|
使用 Vitest 的断言函数验证结果:
|
|
|
|
```typescript
|
|
// 检查基本值
|
|
expect(result.success).toBe(true);
|
|
|
|
// 检查对象部分匹配
|
|
expect(result.data).toMatchObject({
|
|
key: 'value',
|
|
});
|
|
|
|
// 检查数组
|
|
expect(result.items).toHaveLength(2);
|
|
expect(result.items[0].name).toBe('expectedName');
|
|
|
|
// 检查函数调用
|
|
expect(mockFunction).toHaveBeenCalledWith(expectedArgs);
|
|
expect(mockFunction).toHaveBeenCalledTimes(1);
|
|
```
|
|
|
|
## 最佳实践
|
|
|
|
1. **隔离测试**:确保每个测试互不影响,使用 `beforeEach` 重置模拟和状态
|
|
2. **全面覆盖**:测试正常流程、边界条件和错误处理
|
|
3. **清晰命名**:测试名称应清晰描述测试内容和预期结果
|
|
4. **避免测试实现细节**:测试应该关注行为而非实现细节,使代码重构不会破坏测试
|
|
5. **模拟外部依赖**:使用 `vi.mock()` 模拟所有外部依赖,减少测试的不确定性
|
|
|
|
## 示例:测试 IPC 事件处理方法
|
|
|
|
```typescript
|
|
it('应该正确处理 IPC 事件', async () => {
|
|
// 模拟依赖
|
|
mockSomething.mockReturnValue({ result: 'success' });
|
|
|
|
// 调用 IPC 方法
|
|
const result = await controller.ipcMethodName({
|
|
param1: 'value1',
|
|
param2: 'value2',
|
|
});
|
|
|
|
// 验证结果
|
|
expect(result).toEqual({
|
|
success: true,
|
|
data: { result: 'success' },
|
|
});
|
|
|
|
// 验证依赖调用
|
|
expect(mockSomething).toHaveBeenCalledWith('value1', 'value2');
|
|
});
|
|
```
|