mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 23:59:45 +08:00
- Added a new section in the data management documentation for testing, including a link to unified test mocks for Cache, Preference, and DataApi. - Refined the README for test mocks, providing a clearer overview and detailed descriptions of available mocks and their organization. - Improved the structure and clarity of mock usage examples, ensuring better guidance for developers on utilizing the testing utilities effectively.
9.9 KiB
9.9 KiB
Test Mocks
Unified test mocks for the project, organized by process type and globally configured in test setup files.
Overview
Available Mocks
| Process | Mock | Description |
|---|---|---|
| Renderer | CacheService |
Three-tier cache (memory/shared/persist) |
| Renderer | DataApiService |
HTTP client for Data API |
| Renderer | PreferenceService |
User preferences |
| Renderer | useDataApi |
Data API hooks (useQuery, useMutation, etc.) |
| Renderer | usePreference |
Preference hooks |
| Renderer | useCache |
Cache hooks |
| Main | CacheService |
Internal + shared cache |
| Main | DataApiService |
API coordinator |
| Main | PreferenceService |
Preference service |
File Structure
tests/__mocks__/
├── renderer/
│ ├── CacheService.ts
│ ├── DataApiService.ts
│ ├── PreferenceService.ts
│ ├── useDataApi.ts
│ ├── usePreference.ts
│ └── useCache.ts
├── main/
│ ├── CacheService.ts
│ ├── DataApiService.ts
│ └── PreferenceService.ts
├── RendererLoggerService.ts
└── MainLoggerService.ts
Test Setup
Mocks are globally configured in setup files:
- Renderer:
tests/renderer.setup.ts - Main:
tests/main.setup.ts
Renderer Mocks
CacheService
Three-tier cache system with type-safe and casual (dynamic key) methods.
Methods
| Category | Method | Signature |
|---|---|---|
| Memory (typed) | get |
<K>(key: K) => UseCacheSchema[K] |
| Memory (typed) | set |
<K>(key: K, value, ttl?) => void |
| Memory (typed) | has |
<K>(key: K) => boolean |
| Memory (typed) | delete |
<K>(key: K) => boolean |
| Memory (typed) | hasTTL |
<K>(key: K) => boolean |
| Memory (casual) | getCasual |
<T>(key: string) => T | undefined |
| Memory (casual) | setCasual |
<T>(key, value, ttl?) => void |
| Memory (casual) | hasCasual |
(key: string) => boolean |
| Memory (casual) | deleteCasual |
(key: string) => boolean |
| Memory (casual) | hasTTLCasual |
(key: string) => boolean |
| Shared (typed) | getShared |
<K>(key: K) => SharedCacheSchema[K] |
| Shared (typed) | setShared |
<K>(key: K, value, ttl?) => void |
| Shared (typed) | hasShared |
<K>(key: K) => boolean |
| Shared (typed) | deleteShared |
<K>(key: K) => boolean |
| Shared (typed) | hasSharedTTL |
<K>(key: K) => boolean |
| Shared (casual) | getSharedCasual |
<T>(key: string) => T | undefined |
| Shared (casual) | setSharedCasual |
<T>(key, value, ttl?) => void |
| Shared (casual) | hasSharedCasual |
(key: string) => boolean |
| Shared (casual) | deleteSharedCasual |
(key: string) => boolean |
| Shared (casual) | hasSharedTTLCasual |
(key: string) => boolean |
| Persist | getPersist |
<K>(key: K) => RendererPersistCacheSchema[K] |
| Persist | setPersist |
<K>(key: K, value) => void |
| Persist | hasPersist |
(key) => boolean |
| Hook mgmt | registerHook |
(key: string) => void |
| Hook mgmt | unregisterHook |
(key: string) => void |
| Ready state | isSharedCacheReady |
() => boolean |
| Ready state | onSharedCacheReady |
(callback) => () => void |
| Lifecycle | subscribe |
(key, callback) => () => void |
| Lifecycle | cleanup |
() => void |
Usage
import { cacheService } from '@data/CacheService'
import { MockCacheUtils } from 'tests/__mocks__/renderer/CacheService'
describe('Cache', () => {
beforeEach(() => MockCacheUtils.resetMocks())
it('basic usage', () => {
cacheService.setCasual('key', { data: 'value' }, 5000)
expect(cacheService.getCasual('key')).toEqual({ data: 'value' })
})
it('with test utilities', () => {
MockCacheUtils.setInitialState({
memory: [['key', 'value']],
shared: [['shared.key', 'shared']],
persist: [['persist.key', 'persist']]
})
})
})
DataApiService
HTTP client with subscriptions and retry configuration.
Methods
| Method | Signature |
|---|---|
get |
(path, options?) => Promise<any> |
post |
(path, options) => Promise<any> |
put |
(path, options) => Promise<any> |
patch |
(path, options) => Promise<any> |
delete |
(path, options?) => Promise<any> |
subscribe |
(options, callback) => () => void |
configureRetry |
(options) => void |
getRetryConfig |
() => RetryOptions |
getRequestStats |
() => { pendingRequests, activeSubscriptions } |
Usage
import { dataApiService } from '@data/DataApiService'
import { MockDataApiUtils } from 'tests/__mocks__/renderer/DataApiService'
describe('API', () => {
beforeEach(() => MockDataApiUtils.resetMocks())
it('basic request', async () => {
const response = await dataApiService.get('/topics')
expect(response.topics).toBeDefined()
})
it('custom response', async () => {
MockDataApiUtils.setCustomResponse('/topics', 'GET', { custom: true })
const response = await dataApiService.get('/topics')
expect(response.custom).toBe(true)
})
it('error simulation', async () => {
MockDataApiUtils.setErrorResponse('/topics', 'GET', new Error('Failed'))
await expect(dataApiService.get('/topics')).rejects.toThrow('Failed')
})
})
useDataApi Hooks
React hooks for data operations.
Hooks
| Hook | Signature | Returns |
|---|---|---|
useQuery |
(path, options?) |
{ data, loading, error, refetch, mutate } |
useMutation |
(method, path, options?) |
{ mutate, loading, error } |
usePaginatedQuery |
(path, options?) |
{ items, total, page, loading, error, hasMore, hasPrev, prevPage, nextPage, refresh, reset } |
useInvalidateCache |
() |
(keys?) => Promise<any> |
Usage
import { useQuery, useMutation } from '@data/hooks/useDataApi'
import { MockUseDataApiUtils } from 'tests/__mocks__/renderer/useDataApi'
describe('Hooks', () => {
beforeEach(() => MockUseDataApiUtils.resetMocks())
it('useQuery', () => {
const { data, loading } = useQuery('/topics')
expect(loading).toBe(false)
expect(data).toBeDefined()
})
it('useMutation', async () => {
const { mutate } = useMutation('POST', '/topics')
const result = await mutate({ body: { name: 'New' } })
expect(result.created).toBe(true)
})
it('custom data', () => {
MockUseDataApiUtils.mockQueryData('/topics', { custom: true })
const { data } = useQuery('/topics')
expect(data.custom).toBe(true)
})
})
useCache Hooks
React hooks for cache operations.
| Hook | Signature | Returns |
|---|---|---|
useCache |
(key, initValue?) |
[value, setValue] |
useSharedCache |
(key, initValue?) |
[value, setValue] |
usePersistCache |
(key) |
[value, setValue] |
import { useCache } from '@data/hooks/useCache'
const [value, setValue] = useCache('key', 'default')
setValue('new value')
usePreference Hooks
React hooks for preferences.
| Hook | Signature | Returns |
|---|---|---|
usePreference |
(key) |
[value, setValue] |
useMultiplePreferences |
(keyMap) |
[values, setValues] |
import { usePreference } from '@data/hooks/usePreference'
const [theme, setTheme] = usePreference('ui.theme')
await setTheme('dark')
Main Process Mocks
Main CacheService
Internal cache and cross-window shared cache.
Methods
| Category | Method | Signature |
|---|---|---|
| Lifecycle | initialize |
() => Promise<void> |
| Lifecycle | cleanup |
() => void |
| Internal | get |
<T>(key: string) => T | undefined |
| Internal | set |
<T>(key, value, ttl?) => void |
| Internal | has |
(key: string) => boolean |
| Internal | delete |
(key: string) => boolean |
| Shared | getShared |
<K>(key: K) => SharedCacheSchema[K] | undefined |
| Shared | setShared |
<K>(key: K, value, ttl?) => void |
| Shared | hasShared |
<K>(key: K) => boolean |
| Shared | deleteShared |
<K>(key: K) => boolean |
import { MockMainCacheServiceUtils } from 'tests/__mocks__/main/CacheService'
beforeEach(() => MockMainCacheServiceUtils.resetMocks())
MockMainCacheServiceUtils.setCacheValue('key', 'value')
MockMainCacheServiceUtils.setSharedCacheValue('shared.key', 'shared')
Main DataApiService
API coordinator managing ApiServer and IpcAdapter.
| Method | Signature |
|---|---|
initialize |
() => Promise<void> |
shutdown |
() => Promise<void> |
getSystemStatus |
() => object |
getApiServer |
() => ApiServer |
import { MockMainDataApiServiceUtils } from 'tests/__mocks__/main/DataApiService'
beforeEach(() => MockMainDataApiServiceUtils.resetMocks())
MockMainDataApiServiceUtils.simulateInitializationError(new Error('Failed'))
Utility Functions
Each mock exports a MockXxxUtils object with testing utilities:
| Utility | Description |
|---|---|
resetMocks() |
Reset all mock state and call counts |
setXxxValue() |
Set specific values for testing |
getXxxValue() |
Get current mock values |
simulateXxx() |
Simulate specific scenarios (errors, expiration, etc.) |
getMockCallCounts() |
Get call counts for debugging |
Best Practices
- Use global mocks - Don't re-mock in individual tests unless necessary
- Reset in beforeEach - Call
MockXxxUtils.resetMocks()to ensure test isolation - Use utility functions - Prefer
MockXxxUtilsover direct mock manipulation - Type safety - Mocks match actual service interfaces
Troubleshooting
| Issue | Solution |
|---|---|
| Mock not applied | Check test runs in correct process (renderer/main in vitest.config.ts) |
| Type errors | Ensure mock matches actual interface, use type assertions if needed |
| State pollution | Call resetMocks() in beforeEach |
| Import issues | Use path aliases (@data/CacheService) not relative paths |