|
| 1 | +--- |
| 2 | +name: egg-unittest |
| 3 | +description: 本技能用于编写 EGG 应用的单元测试。覆盖 HTTP 接口测试、Service/DI 对象测试、Mock 数据模拟、BackgroundTask 和 EventBus 测试。使用 @eggjs/mock、app.httpRequest()、app.getEggObject()、mm() 等 API。 |
| 4 | +allowed-tools: Read |
| 5 | +--- |
| 6 | + |
| 7 | +# EGG 单元测试 |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## 原理 |
| 12 | + |
| 13 | +`egg-bin test` 使用 Vitest 运行测试,自动完成以下工作: |
| 14 | + |
| 15 | +- 以当前项目目录为 baseDir,创建并启动一个 MockApplication 实例(即 `app`) |
| 16 | +- 注入 `@eggjs/mock/setup_vitest` 管理 app 生命周期(`beforeAll` 启动 app、`afterEach` 恢复 mock、`afterAll` 关闭 app) |
| 17 | +- 注入 Vitest 全局变量(`describe`、`it`、`beforeAll` 等),无需手动 import |
| 18 | + 测试代码中通过 `import { app, mm } from '@eggjs/mock/bootstrap'` 获取已启动的 app 实例和 mock 工具,直接使用即可。 |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## 配置检查 |
| 23 | + |
| 24 | +确保项目中有以下配置: |
| 25 | + |
| 26 | +**package.json:** |
| 27 | + |
| 28 | +```json |
| 29 | +{ |
| 30 | + "scripts": { |
| 31 | + "test": "egg-bin test" |
| 32 | + }, |
| 33 | + "devDependencies": { |
| 34 | + "@eggjs/bin": "^8", |
| 35 | + "@eggjs/mock": "^8" |
| 36 | + } |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +**测试文件约定:** |
| 41 | + |
| 42 | +- 测试目录:`test/` |
| 43 | +- 文件命名:`*.test.ts` |
| 44 | + |
| 45 | +**自定义 setup 文件(可选):** |
| 46 | + |
| 47 | +如果存在 `test/.setup.ts`,egg-bin 会自动将其加入 vitest setupFiles,在 `@eggjs/mock/setup_vitest` 之前执行(即 app 启动之前)。可用于设置环境变量等全局初始化: |
| 48 | + |
| 49 | +```typescript |
| 50 | +// test/.setup.ts |
| 51 | +beforeAll(() => { |
| 52 | + process.env.SOME_CONFIG = 'test-value'; |
| 53 | +}); |
| 54 | +``` |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +## 简单示例 |
| 59 | + |
| 60 | +### HTTP 接口测试 |
| 61 | + |
| 62 | +```typescript |
| 63 | +import assert from 'node:assert'; |
| 64 | +import { app } from '@eggjs/mock/bootstrap'; |
| 65 | + |
| 66 | +describe('test/controller/home.test.ts', () => { |
| 67 | + it('should GET /', () => { |
| 68 | + return app.httpRequest() |
| 69 | + .get('/') |
| 70 | + .expect(200) |
| 71 | + .expect('hello world'); |
| 72 | + }); |
| 73 | +}); |
| 74 | +``` |
| 75 | + |
| 76 | +### Service 测试 |
| 77 | + |
| 78 | +```typescript |
| 79 | +import assert from 'node:assert'; |
| 80 | +import { app } from '@eggjs/mock/bootstrap'; |
| 81 | +import { UserService } from '../app/modules/user/UserService.ts'; |
| 82 | + |
| 83 | +describe('test/service/user.test.ts', () => { |
| 84 | + it('should get user', async () => { |
| 85 | + const userService = await app.getEggObject(UserService); |
| 86 | + const user = await userService.getById('1'); |
| 87 | + assert(user); |
| 88 | + assert.equal(user.name, 'test'); |
| 89 | + }); |
| 90 | +}); |
| 91 | +``` |
| 92 | + |
| 93 | +--- |
| 94 | + |
| 95 | +## 测试场景决策树 |
| 96 | + |
| 97 | +``` |
| 98 | +要测什么? |
| 99 | +
|
| 100 | +1. HTTP 接口(GET/POST/PUT/DELETE)? |
| 101 | + → 参考 references/http-test.md |
| 102 | +
|
| 103 | +2. Service / DI 对象的方法? |
| 104 | + → 参考 references/service-test.md |
| 105 | +
|
| 106 | +3. 需要 mock 外部依赖?(HTTP 调用、Service 方法、Session、CSRF) |
| 107 | + → 参考 references/mock.md |
| 108 | +
|
| 109 | +4. BackgroundTaskHelper(后台异步任务)? |
| 110 | + → 参考 references/background-task-test.md |
| 111 | +
|
| 112 | +5. EventBus(事件驱动)? |
| 113 | + → 参考 references/eventbus-test.md |
| 114 | +``` |
| 115 | + |
| 116 | +--- |
| 117 | + |
| 118 | +## 快速参考 |
| 119 | + |
| 120 | +| API | 说明 | |
| 121 | +| ---------------------------------------------------- | ------------------------------- | |
| 122 | +| `import { app, mm } from '@eggjs/mock/bootstrap'` | 标准测试入口 | |
| 123 | +| `app.httpRequest().get('/path').expect(200)` | HTTP 接口测试 | |
| 124 | +| `app.getEggObject(Class)` | 获取 Singleton 实例 | |
| 125 | +| `app.mockModuleContextScope(async (ctx) => { ... })` | ContextProto 测试作用域 | |
| 126 | +| `mm(Class.prototype, 'method', fn)` | Mock Proto 方法 | |
| 127 | +| `app.mockCsrf()` | 跳过 CSRF 校验(POST 测试必备) | |
| 128 | +| `app.mockHttpclient(url, data)` | Mock 外部 HTTP 调用 | |
| 129 | +| `app.getEventWaiter()` | 获取 EventBus 事件等待器 | |
| 130 | + |
| 131 | +--- |
| 132 | + |
| 133 | +## 常见错误 |
| 134 | + |
| 135 | +| 错误写法 | 正确写法 | 说明 | |
| 136 | +| -------------------------------------- | ----------------------------------------------------------- | ------------------------------------- | |
| 137 | +| `import { app } from 'egg'` | `import { app } from '@eggjs/mock/bootstrap'` | 测试使用 mock 包 | |
| 138 | +| `before()` / `after()` | `beforeAll()` / `afterAll()` | Vitest 钩子,不是 Mocha | |
| 139 | +| POST 测试报 403 | 加 `app.mockCsrf()` | 安全插件默认开启 CSRF | |
| 140 | +| 手动写 `afterEach(mm.restore)` | 不需要 | egg-bin 自动注入 mock 恢复 | |
| 141 | +| `app.getEggObject()` 获取 ContextProto | 在 `app.mockModuleContextScope()` 内用 `ctx.getEggObject()` | `app.getEggObject` 只能获取 Singleton | |
| 142 | +| 代码写在 describe 内、hooks 外 | 放入 `beforeAll` / `beforeEach` | describe 体在加载阶段就执行 | |
| 143 | +| `await app.ready()` 配合 bootstrap | 不需要 | bootstrap 自动处理生命周期 | |
| 144 | + |
| 145 | +--- |
| 146 | + |
| 147 | +## 参考资料 |
| 148 | + |
| 149 | +- `references/http-test.md` — HTTP 接口测试 |
| 150 | +- `references/service-test.md` — Service/DI 对象测试 |
| 151 | +- `references/mock.md` — Mock 模式 |
| 152 | +- `references/background-task-test.md` — BackgroundTaskHelper 测试 |
| 153 | +- `references/eventbus-test.md` — EventBus 测试 |
0 commit comments