# Mini UI 包测试规范
**版本**: 1.0
**创建日期**: 2025-12-26
**适用范围**: 所有 Taro 小程序 UI 包测试
## 概述
本文档规定了 Mini UI 包的测试编写规范,基于故事 017.003 的实施经验总结。遵循这些规范可以确保测试的一致性、可维护性和正确性。
## 核心原则
### 1. 使用 Jest,不是 Vitest
**重要**: Mini UI 包使用 **Jest** 测试框架,不是 Vitest。
```javascript
// jest.config.cjs
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
// ...
}
```
### 2. 使用共享的 mini-testing-utils
**关键原则**: 不要在每个 UI 包中重写 Taro mock,直接使用 `@d8d/mini-testing-utils` 提供的共享 mock。
```javascript
// jest.config.cjs
module.exports = {
setupFilesAfterEnv: ['@d8d/mini-testing-utils/testing/setup'],
moduleNameMapper: {
'^@tarojs/taro$': '@d8d/mini-testing-utils/testing/taro-api-mock.ts',
}
}
```
### 3. 使用真实的 React Query
**关键原则**: 使用真实的 React Query(不要 mock),以便验证 RPC 类型推断。
```typescript
// ✅ 正确: 使用真实的 React Query
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const createTestWrapper = () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false, staleTime: Infinity }
}
})
return ({ children }: { children: React.ReactNode }) => (
{children}
)
}
```
```typescript
// ❌ 错误: 不要 mock React Query
jest.mock('@tanstack/react-query', () => ({
useQuery: jest.fn()
}))
```
## Jest 配置规范
### 标准 jest.config.cjs 模板
```javascript
// jest.config.cjs
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
// 使用 mini-testing-utils 提供的共享 setup
setupFilesAfterEnv: ['@d8d/mini-testing-utils/testing/setup'],
moduleNameMapper: {
// 测试文件中的别名映射(仅用于测试文件)
'^@/(.*)$': '/src/$1',
'^~/(.*)$': '/tests/$1',
// Taro API 重定向到共享 mock
'^@tarojs/taro$': '@d8d/mini-testing-utils/testing/taro-api-mock.ts',
// 样式和文件映射
'\\.(css|less|scss|sass)$': '@d8d/mini-testing-utils/testing/style-mock.js',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'@d8d/mini-testing-utils/testing/file-mock.js'
},
testMatch: [
'/tests/**/*.spec.{ts,tsx}',
'/tests/**/*.test.{ts,tsx}'
],
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/**/index.{ts,tsx}',
'!src/**/*.stories.{ts,tsx}'
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
testPathIgnorePatterns: [
'/node_modules/',
'/dist/',
'/coverage/'
],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
'^.+\\.(js|jsx)$': 'babel-jest'
},
transformIgnorePatterns: [
'/node_modules/(?!(swiper|@tarojs)/)'
],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json']
}
```
## 测试文件结构规范
```
mini-ui-packages//
└── tests/
├── unit/ # 单元测试
│ └── components/
│ ├── ComponentName.test.tsx
│ └── ...
└── pages/ # 页面组件测试
└── PageName/
└── PageName.test.tsx
```
## 测试编写规范
### 1. 组件测试模板
```typescript
/**
* ComponentName 组件测试
*/
import React from 'react'
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import ComponentName, { PropsInterface } from '../../../src/components/ComponentName'
import Taro from '@tarojs/taro'
describe('ComponentName', () => {
const mockProps: PropsInterface = {
// mock props
}
beforeEach(() => {
// 清理 Taro API mock
;(Taro.someApi as jest.Mock).mockClear()
})
it('应该正确渲染', () => {
render()
expect(screen.getByText('expected text')).toBeInTheDocument()
})
})
```
### 2. 页面组件测试模板
```typescript
/**
* PageName 页面测试
* 使用真实的React Query和RPC类型验证
*/
import React from 'react'
import { render, screen, waitFor } from '@testing-library/react'
import '@testing-library/jest-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import PageName from '../../../src/pages/PageName/PageName'
import { apiClient } from '../../../src/api'
import { useRequireAuth } from '@d8d/xxx-auth-ui/hooks'
import Taro from '@tarojs/taro'
// Mock auth hooks
jest.mock('@d8d/xxx-auth-ui/hooks', () => ({
useRequireAuth: jest.fn()
}))
// Mock API client - 使用真实的RPC类型
jest.mock('../../../src/api', () => ({
apiClient: {
route: {
$get: jest.fn()
}
}
}))
const createMockResponse = (status: number, data: T) => ({
status,
ok: status >= 200 && status < 300,
json: async () => data
})
const createTestWrapper = () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
staleTime: Infinity,
refetchOnWindowFocus: false
}
}
})
return ({ children }: { children: React.ReactNode }) => (
{children}
)
}
describe('PageName', () => {
beforeEach(() => {
jest.clearAllMocks()
// Mock useRequireAuth to do nothing (user is authenticated)
;(useRequireAuth as jest.Mock).mockImplementation(() => {})
// Reset Taro API mocks
;(Taro.setNavigationBarTitle as jest.Mock).mockClear()
;(Taro.showToast as jest.Mock).mockClear()
})
it('应该渲染带有正确数据的页面', async () => {
// Mock API calls - 使用符合RPC类型的响应
;(apiClient.route.$get as jest.Mock).mockResolvedValue(
createMockResponse(200, mockData)
)
const wrapper = createTestWrapper()
render(, { wrapper })
await waitFor(() => {
expect(screen.getByText('expected text')).toBeInTheDocument()
})
})
})
```
### 3. Mock RPC 响应规范
```typescript
// ✅ 正确: 使用 Mock 响应工具函数
const createMockResponse = (status: number, data: T) => ({
status,
ok: status >= 200 && status < 300,
json: async () => data
})
// 使用示例
; (apiClient.route.$get as jest.Mock).mockResolvedValue(
createMockResponse(200, { name: '张三', age: 35 })
)
```
### 4. Taro API 使用规范
```typescript
// ✅ 正确: 直接使用 Taro API,不需要自定义 mock
import Taro from '@tarojs/taro'
describe('Component', () => {
beforeEach(() => {
// 清理 mock
;(Taro.showToast as jest.Mock).mockClear()
})
it('应该调用Taro API', () => {
render()
expect(Taro.showToast).toHaveBeenCalledWith({ title: '成功' })
})
})
// ❌ 错误: 不要在测试文件中自定义 Taro mock
jest.mock('@tarojs/taro', () => ({
default: {
showToast: jest.fn() // 错误!应该使用 mini-testing-utils 的共享 mock
}
}))
```
## 测试最佳实践
### 1. RPC 类型推断验证
使用真实的 React Query 来验证 RPC 类型推断:
```typescript
// ✅ 正确: 测试中使用真实的 RPC 类型
const mockData = {
name: '张三',
age: 35,
// 类型错误会在编译时被捕获
wrongField: 'type error' // TypeScript 会报错
}
; (apiClient.route.$get as jest.Mock).mockResolvedValue(
createMockResponse(200, mockData)
)
```
### 2. 异步测试
使用 `waitFor` 处理异步状态更新:
```typescript
it('应该在加载完成后渲染数据', async () => {
; (apiClient.route.$get as jest.Mock).mockResolvedValue(
createMockResponse(200, mockData)
)
render(, { wrapper })
await waitFor(() => {
expect(screen.getByText('data')).toBeInTheDocument()
})
})
```
### 3. 用户交互测试
```typescript
it('应该处理点击事件', () => {
const { fireEvent } = require('@testing-library/react')
render()
const button = screen.getByText('Click me')
fireEvent.click(button)
expect(Taro.someApi).toHaveBeenCalled()
})
```
## 常见错误
### 错误 1: 重写 Taro mock
```typescript
// ❌ 错误: 在测试文件中重写 Taro mock
jest.mock('@tarojs/taro', () => ({
default: {
showToast: jest.fn()
}
}))
// ✅ 正确: 直接导入使用
import Taro from '@tarojs/taro'
// mock 在 mini-testing-utils/testing/setup.ts 中已配置
```
### 错误 2: Mock React Query
```typescript
// ❌ 错误: Mock React Query
jest.mock('@tanstack/react-query', () => ({
useQuery: jest.fn(() => ({ data: mockData }))
}))
// ✅ 正确: 使用真实的 React Query
const wrapper = createTestWrapper()
render(, { wrapper })
```
### 错误 3: 在 UI 包源代码中使用别名
```typescript
// ❌ 错误: 源代码中使用别名
import { apiClient } from '@/api'
import { Component } from '@/components'
// ✅ 正确: 源代码中使用相对路径
import { apiClient } from '../api'
import { Component } from '../components'
```
### 错误 4: 忘记清理 mock
```typescript
// ❌ 错误: 忘记清理 mock
describe('Component', () => {
it('test 1', () => {
expect(Taro.showToast).toHaveBeenCalledTimes(1)
})
it('test 2', () => {
// test 1 的 mock 调用会影响 test 2
expect(Taro.showToast).toHaveBeenCalledTimes(1) // 可能失败
})
})
// ✅ 正确: 在 beforeEach 中清理 mock
describe('Component', () => {
beforeEach(() => {
; (Taro.showToast as jest.Mock).mockClear()
})
it('test 1', () => {
expect(Taro.showToast).toHaveBeenCalledTimes(1)
})
it('test 2', () => {
// mock 已清理,计数重新开始
expect(Taro.showToast).toHaveBeenCalledTimes(1)
})
})
```
## 测试执行
```bash
# 运行所有测试
cd mini-ui-packages/ && pnpm test
# 运行特定测试
pnpm test --testNamePattern="ComponentName"
# 生成覆盖率报告
pnpm test:coverage
# 监听模式
pnpm test:watch
```
## mini-testing-utils 扩展
如果需要添加新的 Taro API mock,在 `mini-testing-utils/testing/taro-api-mock.ts` 中添加:
```typescript
// 1. 添加 mock 函数声明
export const mockNewApi = jest.fn()
// 2. 在默认导出中添加
export default {
// ...
newApi: mockNewApi
}
// 3. 在命名导出中添加
export {
// ...
mockNewApi as newApi
}
```
## 参考资料
- [Jest 官方文档](https://jestjs.io/)
- [Testing Library](https://testing-library.com/)
- [Taro 官方文档](https://taro-docs.jd.com/)
- [mini-ui-package-standards.md](./mini-ui-package-standards.md) - Mini UI 包开发规范
- [testing-strategy.md](./testing-strategy.md) - 通用测试策略
## 版本历史
| 版本 | 日期 | 描述 | 作者 |
|------|------|------|------|
| 1.0 | 2025-12-26 | 初始版本,基于故事 017.003 实施经验 | Dev Agent |