|
|
||
|---|---|---|
| .. | ||
| main | ||
| renderer | ||
| MainLoggerService.ts | ||
| README.md | ||
| RendererLoggerService.ts | ||
Test Mocks
这个目录包含了项目中使用的统一测试模拟(mocks)。这些模拟按照进程类型组织,避免重名冲突,并在相应的测试设置文件中全局配置。
🎯 统一模拟概述
已实现的统一模拟
Renderer Process Mocks
- ✅ PreferenceService - 渲染进程偏好设置服务模拟
- ✅ DataApiService - 渲染进程数据API服务模拟
- ✅ CacheService - 渲染进程三层缓存服务模拟
- ✅ useDataApi hooks - 数据API钩子模拟 (useQuery, useMutation, usePaginatedQuery, etc.)
- ✅ usePreference hooks - 偏好设置钩子模拟 (usePreference, useMultiplePreferences)
- ✅ useCache hooks - 缓存钩子模拟 (useCache, useSharedCache, usePersistCache)
Main Process Mocks
- ✅ PreferenceService - 主进程偏好设置服务模拟
- ✅ DataApiService - 主进程数据API服务模拟
- ✅ CacheService - 主进程缓存服务模拟
🌟 核心优势
- 进程分离: 按照renderer/main分开组织,避免重名冲突
- 自动应用: 无需在每个测试文件中单独模拟
- 完整API覆盖: 实现了所有服务和钩子的完整API
- 类型安全: 完全支持 TypeScript,保持与真实服务的类型兼容性
- 现实行为: 模拟提供现实的默认值和行为模式
- 高度可定制: 支持为特定测试定制行为
- 测试工具: 内置丰富的测试工具函数
📁 文件结构
tests/__mocks__/
├── README.md # 本文档
├── renderer/ # 渲染进程模拟
│ ├── PreferenceService.ts # 渲染进程偏好设置服务模拟
│ ├── DataApiService.ts # 渲染进程数据API服务模拟
│ ├── CacheService.ts # 渲染进程缓存服务模拟
│ ├── useDataApi.ts # 数据API钩子模拟
│ ├── usePreference.ts # 偏好设置钩子模拟
│ └── useCache.ts # 缓存钩子模拟
├── main/ # 主进程模拟
│ ├── PreferenceService.ts # 主进程偏好设置服务模拟
│ ├── DataApiService.ts # 主进程数据API服务模拟
│ └── CacheService.ts # 主进程缓存服务模拟
├── RendererLoggerService.ts # 渲染进程日志服务模拟
└── MainLoggerService.ts # 主进程日志服务模拟
🔧 测试设置
Renderer Process Tests
在 tests/renderer.setup.ts 中配置了所有渲染进程模拟:
// 自动加载 renderer/ 目录下的模拟
vi.mock('@data/PreferenceService', async () => {
const { MockPreferenceService } = await import('./__mocks__/renderer/PreferenceService')
return MockPreferenceService
})
// ... 其他渲染进程模拟
Main Process Tests
在 tests/main.setup.ts 中配置了所有主进程模拟:
// 自动加载 main/ 目录下的模拟
vi.mock('@main/data/PreferenceService', async () => {
const { MockMainPreferenceServiceExport } = await import('./__mocks__/main/PreferenceService')
return MockMainPreferenceServiceExport
})
// ... 其他主进程模拟
PreferenceService Mock
简介
PreferenceService.ts 提供了 PreferenceService 的统一模拟实现,用于所有渲染进程测试。这个模拟:
- ✅ 自动应用:在
renderer.setup.ts中全局配置,无需在每个测试文件中单独模拟 - ✅ 完整API:实现了 PreferenceService 的所有方法(get, getMultiple, set, etc.)
- ✅ 合理默认值:提供了常用偏好设置的默认值
- ✅ 可定制:支持为特定测试定制默认值
- ✅ 类型安全:完全支持 TypeScript 类型检查
默认值
模拟提供了以下默认偏好设置:
// 导出偏好设置
'data.export.markdown.force_dollar_math': false
'data.export.markdown.exclude_citations': false
'data.export.markdown.standardize_citations': true
'data.export.markdown.show_model_name': false
'data.export.markdown.show_model_provider': false
// UI偏好设置
'ui.language': 'en'
'ui.theme': 'light'
'ui.font_size': 14
// AI偏好设置
'ai.default_model': 'gpt-4'
'ai.temperature': 0.7
'ai.max_tokens': 2000
// 功能开关
'feature.web_search': true
'feature.reasoning': false
'feature.tool_calling': true
基本使用
由于模拟已经全局配置,大多数测试可以直接使用 PreferenceService,无需额外设置:
import { preferenceService } from '@data/PreferenceService'
describe('MyComponent', () => {
it('should use preference values', async () => {
// PreferenceService 已经被自动模拟
const value = await preferenceService.get('ui.theme')
expect(value).toBe('light') // 使用默认值
})
})
高级使用
1. 修改单个测试的偏好值
import { preferenceService } from '@data/PreferenceService'
import { vi } from 'vitest'
describe('Custom preferences', () => {
it('should work with custom preference values', async () => {
// 为这个测试修改特定值
;(preferenceService.get as any).mockImplementation((key: string) => {
if (key === 'ui.theme') return Promise.resolve('dark')
// 其他键使用默认模拟行为
return vi.fn().mockResolvedValue(null)()
})
const theme = await preferenceService.get('ui.theme')
expect(theme).toBe('dark')
})
})
2. 重置模拟状态
import { preferenceService } from '@data/PreferenceService'
describe('Mock state management', () => {
beforeEach(() => {
// 重置模拟到初始状态
if ('_resetMockState' in preferenceService) {
;(preferenceService as any)._resetMockState()
}
})
})
3. 检查模拟内部状态
import { preferenceService } from '@data/PreferenceService'
describe('Mock inspection', () => {
it('should allow inspecting mock state', () => {
// 查看当前模拟状态
if ('_getMockState' in preferenceService) {
const state = (preferenceService as any)._getMockState()
console.log('Current mock state:', state)
}
})
})
4. 为整个测试套件定制默认值
如果需要为特定的测试文件定制默认值,可以在该文件中重新模拟:
import { vi } from 'vitest'
// 重写全局模拟,添加自定义默认值
vi.mock('@data/PreferenceService', async () => {
const { createMockPreferenceService } = await import('tests/__mocks__/PreferenceService')
// 定制默认值
const customDefaults = {
'my.custom.setting': 'custom_value',
'ui.theme': 'dark' // 覆盖默认值
}
return {
preferenceService: createMockPreferenceService(customDefaults)
}
})
测试验证
可以验证 PreferenceService 方法是否被正确调用:
import { preferenceService } from '@data/PreferenceService'
import { vi } from 'vitest'
describe('Preference service calls', () => {
it('should call preference service methods', async () => {
await preferenceService.get('ui.theme')
// 验证方法调用
expect(preferenceService.get).toHaveBeenCalledWith('ui.theme')
expect(preferenceService.get).toHaveBeenCalledTimes(1)
})
})
添加新的默认值
当项目中添加新的偏好设置时,请在 PreferenceService.ts 的 mockPreferenceDefaults 中添加相应的默认值:
export const mockPreferenceDefaults: Record<string, any> = {
// 现有默认值...
// 新增默认值
'new.feature.enabled': true,
'new.feature.config': { option: 'value' }
}
这样可以确保所有测试都能使用合理的默认值,减少测试失败的可能性。
DataApiService Mock
简介
DataApiService.ts 提供了数据API服务的统一模拟,支持所有HTTP方法和高级功能。
功能特性
- 完整HTTP支持: GET, POST, PUT, PATCH, DELETE
- 批量操作: batch() 和 transaction() 支持
- 订阅系统: subscribe/unsubscribe 模拟
- 连接管理: connect/disconnect/ping 方法
- 智能模拟数据: 基于路径自动生成合理的响应
基本使用
import { dataApiService } from '@data/DataApiService'
describe('API Integration', () => {
it('should fetch topics', async () => {
// 自动模拟,返回预设的主题列表
const response = await dataApiService.get('/api/topics')
expect(response.success).toBe(true)
expect(response.data.topics).toHaveLength(2)
})
})
高级使用
import { MockDataApiUtils } from 'tests/__mocks__/DataApiService'
describe('Custom API behavior', () => {
beforeEach(() => {
MockDataApiUtils.resetMocks()
})
it('should handle custom responses', async () => {
// 设置特定路径的自定义响应
MockDataApiUtils.setCustomResponse('/api/topics', 'GET', {
topics: [{ id: 'custom', name: 'Custom Topic' }]
})
const response = await dataApiService.get('/api/topics')
expect(response.data.topics[0].name).toBe('Custom Topic')
})
it('should simulate errors', async () => {
// 模拟错误响应
MockDataApiUtils.setErrorResponse('/api/topics', 'GET', 'Network error')
const response = await dataApiService.get('/api/topics')
expect(response.success).toBe(false)
expect(response.error?.message).toBe('Network error')
})
})
CacheService Mock
简介
CacheService.ts 提供了三层缓存系统的完整模拟:内存缓存、共享缓存和持久化缓存。
功能特性
- 三层架构: 内存、共享、持久化缓存
- 订阅系统: 支持缓存变更订阅
- TTL支持: 模拟缓存过期(简化版)
- Hook引用跟踪: 模拟生产环境的引用管理
- 默认值: 基于缓存schema的智能默认值
基本使用
import { cacheService } from '@data/CacheService'
describe('Cache Operations', () => {
it('should store and retrieve cache values', () => {
// 设置缓存值
cacheService.set('user.preferences', { theme: 'dark' })
// 获取缓存值
const preferences = cacheService.get('user.preferences')
expect(preferences.theme).toBe('dark')
})
it('should work with persist cache', () => {
// 持久化缓存操作
cacheService.setPersist('app.last_opened_topic', 'topic123')
const lastTopic = cacheService.getPersist('app.last_opened_topic')
expect(lastTopic).toBe('topic123')
})
})
高级测试工具
import { MockCacheUtils } from 'tests/__mocks__/CacheService'
describe('Advanced cache testing', () => {
beforeEach(() => {
MockCacheUtils.resetMocks()
})
it('should set initial cache state', () => {
// 设置初始缓存状态
MockCacheUtils.setInitialState({
memory: [['theme', 'dark'], ['language', 'en']],
persist: [['app.version', '1.0.0']]
})
expect(cacheService.get('theme')).toBe('dark')
expect(cacheService.getPersist('app.version')).toBe('1.0.0')
})
it('should simulate cache changes', () => {
let changeCount = 0
cacheService.subscribe('theme', () => changeCount++)
MockCacheUtils.triggerCacheChange('theme', 'light')
expect(changeCount).toBe(1)
})
})
useDataApi Hooks Mock
简介
useDataApi.ts 提供了所有数据API钩子的统一模拟,包括查询、变更和分页功能。
支持的钩子
useQuery- 数据查询钩子useMutation- 数据变更钩子usePaginatedQuery- 分页查询钩子useInvalidateCache- 缓存失效钩子prefetch- 预取函数
基本使用
import { useQuery, useMutation } from '@data/hooks/useDataApi'
describe('Data API Hooks', () => {
it('should work with useQuery', () => {
const { data, isLoading, error } = useQuery('/api/topics')
// 默认返回模拟数据
expect(data).toBeDefined()
expect(data.topics).toHaveLength(2)
expect(isLoading).toBe(false)
expect(error).toBeUndefined()
})
it('should work with useMutation', async () => {
const { trigger, isMutating } = useMutation('/api/topics', 'POST')
const result = await trigger({ name: 'New Topic' })
expect(result.created).toBe(true)
expect(result.name).toBe('New Topic')
})
})
自定义测试行为
import { MockUseDataApiUtils } from 'tests/__mocks__/useDataApi'
describe('Custom hook behavior', () => {
beforeEach(() => {
MockUseDataApiUtils.resetMocks()
})
it('should mock loading state', () => {
MockUseDataApiUtils.mockQueryLoading('/api/topics')
const { data, isLoading } = useQuery('/api/topics')
expect(isLoading).toBe(true)
expect(data).toBeUndefined()
})
it('should mock error state', () => {
const error = new Error('API Error')
MockUseDataApiUtils.mockQueryError('/api/topics', error)
const { data, error: queryError } = useQuery('/api/topics')
expect(queryError).toBe(error)
expect(data).toBeUndefined()
})
})
usePreference Hooks Mock
简介
usePreference.ts 提供了偏好设置钩子的统一模拟,支持单个和批量偏好管理。
支持的钩子
usePreference- 单个偏好设置钩子useMultiplePreferences- 多个偏好设置钩子
基本使用
import { usePreference, useMultiplePreferences } from '@data/hooks/usePreference'
describe('Preference Hooks', () => {
it('should work with usePreference', async () => {
const [theme, setTheme] = usePreference('ui.theme')
expect(theme).toBe('light') // 默认值
await setTheme('dark')
// 在测试中,可以通过工具函数验证值是否更新
})
it('should work with multiple preferences', async () => {
const [prefs, setPrefs] = useMultiplePreferences({
theme: 'ui.theme',
lang: 'ui.language'
})
expect(prefs.theme).toBe('light')
expect(prefs.lang).toBe('en')
await setPrefs({ theme: 'dark' })
})
})
高级测试
import { MockUsePreferenceUtils } from 'tests/__mocks__/usePreference'
describe('Advanced preference testing', () => {
beforeEach(() => {
MockUsePreferenceUtils.resetMocks()
})
it('should simulate preference changes', () => {
MockUsePreferenceUtils.setPreferenceValue('ui.theme', 'dark')
const [theme] = usePreference('ui.theme')
expect(theme).toBe('dark')
})
it('should simulate external changes', () => {
let callCount = 0
MockUsePreferenceUtils.addSubscriber('ui.theme', () => callCount++)
MockUsePreferenceUtils.simulateExternalPreferenceChange('ui.theme', 'dark')
expect(callCount).toBe(1)
})
})
useCache Hooks Mock
简介
useCache.ts 提供了缓存钩子的统一模拟,支持三种缓存层级。
支持的钩子
useCache- 内存缓存钩子useSharedCache- 共享缓存钩子usePersistCache- 持久化缓存钩子
基本使用
import { useCache, useSharedCache, usePersistCache } from '@data/hooks/useCache'
describe('Cache Hooks', () => {
it('should work with useCache', () => {
const [theme, setTheme] = useCache('ui.theme', 'light')
expect(theme).toBe('light')
setTheme('dark')
// 值立即更新
})
it('should work with different cache types', () => {
const [shared, setShared] = useSharedCache('app.window_count', 1)
const [persist, setPersist] = usePersistCache('app.last_version', '1.0.0')
expect(shared).toBe(1)
expect(persist).toBe('1.0.0')
})
})
测试工具
import { MockUseCacheUtils } from 'tests/__mocks__/useCache'
describe('Cache hook testing', () => {
beforeEach(() => {
MockUseCacheUtils.resetMocks()
})
it('should set initial cache state', () => {
MockUseCacheUtils.setMultipleCacheValues({
memory: [['ui.theme', 'dark']],
shared: [['app.mode', 'development']],
persist: [['user.id', 'user123']]
})
const [theme] = useCache('ui.theme')
const [mode] = useSharedCache('app.mode')
const [userId] = usePersistCache('user.id')
expect(theme).toBe('dark')
expect(mode).toBe('development')
expect(userId).toBe('user123')
})
})
LoggerService Mock
简介
项目还包含了 LoggerService 的模拟:
RendererLoggerService.ts- 渲染进程日志服务模拟MainLoggerService.ts- 主进程日志服务模拟
这些模拟同样在相应的测试设置文件中全局配置。
最佳实践
- 优先使用全局模拟:大多数情况下应该直接使用全局配置的模拟,而不是在每个测试中单独模拟
- 合理的默认值:确保模拟的默认值反映实际应用的常见配置
- 文档更新:当添加新的模拟或修改现有模拟时,请更新相关文档
- 类型安全:保持模拟与实际服务的类型兼容性
- 测试隔离:如果需要修改模拟行为,确保在测试后恢复或在 beforeEach 中重置
故障排除
模拟未生效
如果发现 PreferenceService 模拟未生效:
- 确认测试运行在渲染进程环境中(
vitest.config.ts中的renderer项目) - 检查
tests/renderer.setup.ts是否正确配置 - 确认导入路径使用的是
@data/PreferenceService而非相对路径
类型错误
如果遇到 TypeScript 类型错误:
- 确认模拟实现与实际 PreferenceService 接口匹配
- 在测试中使用类型断言:
(preferenceService as any)._getMockState() - 检查是否需要更新模拟的类型定义