333
schangxiang@126.com
2025-09-19 18966e02fb573c7e2bb0c6426ed792b38b910940
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# egg-mock
 
[![NPM version][npm-image]][npm-url]
[![build status][travis-image]][travis-url]
[![Test coverage][codecov-image]][codecov-url]
[![David deps][david-image]][david-url]
[![Known Vulnerabilities][snyk-image]][snyk-url]
[![npm download][download-image]][download-url]
 
[npm-image]: https://img.shields.io/npm/v/egg-mock.svg?style=flat-square
[npm-url]: https://npmjs.org/package/egg-mock
[travis-image]: https://img.shields.io/travis/eggjs/egg-mock.svg?style=flat-square
[travis-url]: https://travis-ci.org/eggjs/egg-mock
[codecov-image]: https://codecov.io/github/eggjs/egg-mock/coverage.svg?branch=master
[codecov-url]: https://codecov.io/github/eggjs/egg-mock?branch=master
[david-image]: https://img.shields.io/david/eggjs/egg-mock.svg?style=flat-square
[david-url]: https://david-dm.org/eggjs/egg-mock
[snyk-image]: https://snyk.io/test/npm/egg-mock/badge.svg?style=flat-square
[snyk-url]: https://snyk.io/test/npm/egg-mock
[download-image]: https://img.shields.io/npm/dm/egg-mock.svg?style=flat-square
[download-url]: https://npmjs.org/package/egg-mock
 
一个数据模拟的库,更方便地测试 Egg 应用、插件及自定义 Egg 框架。`egg-mock` 拓展自 [node_modules/mm](https://github.com/node-modules/mm),你可以使用所有 `mm` 包含的 API。
 
## Install
 
```bash
$ npm i egg-mock --save-dev
```
 
## Usage
 
### 创建测试用例
 
通过 `mm.app` 启动应用,可以使用 App 的 API 模拟数据
 
```js
// test/index.test.js
const path = require('path');
const mm = require('egg-mock');
 
describe('some test', () => {
  let app;
  before(() => {
    app = mm.app({
      baseDir: 'apps/foo'
      customEgg: path.join(__dirname, '../node_modules/egg'),
    });
    return app.ready();
  })
  after(() => app.close());
 
  it('should request /', () => {
    return app.httpRequest()
      .get('/')
      .expect(200);
  });
});
```
 
使用 `mm.app` 启动后可以通过 `app.agent` 访问到 agent 对象。
 
使用 `mm.cluster` 启动多进程测试,API 与 `mm.app` 一致。
 
### 应用开发者
 
应用开发者不需要传入 baseDir,其为当前路径
 
```js
before(() => {
  app = mm.app({
    customEgg: path.join(__dirname, '../node_modules/egg'),
  });
  return app.ready();
});
```
 
### 框架开发者
 
框架开发者需要指定 customEgg,会将当前路径指定为框架入口
 
```js
before(() => {
  app = mm.app({
    baseDir: 'apps/demo',
    customEgg: true,
  });
  return app.ready();
});
```
 
### 插件开发者
 
在插件目录下执行测试用例时,只要 `package.json` 中有 `eggPlugin.name` 字段,就会自动把当前目录加到插件列表中。
 
```js
before(() => {
  app = mm.app({
    baseDir: 'apps/demo',
    customEgg: path.join(__dirname, '../node_modules/egg'),
  });
  return app.ready();
});
```
 
也可以通过 customEgg 指定其他框架,比如希望在 aliyun-egg 和 framework-b 同时测试此插件。
 
```js
describe('aliyun-egg', () => {
  let app;
  before(() => {
    app = mm.app({
      baseDir: 'apps/demo',
      customEgg: path.join(__dirname, 'node_modules/aliyun-egg'),
    });
    return app.ready();
  });
});
 
describe('framework-b', () => {
  let app;
  before(() => {
    app = mm.app({
      baseDir: 'apps/demo',
      customEgg: path.join(__dirname, 'node_modules/framework-b'),
    });
    return app.ready();
  });
});
```
 
如果当前目录确实是一个 egg 插件,但是又不想当它是一个插件来测试,可以通过 `options.plugin` 选项来关闭:
 
```js
before(() => {
  app = mm.app({
    baseDir: 'apps/demo',
    customEgg: path.join(__dirname, 'node_modules/egg'),
    plugin: false,
  });
  return app.ready();
});
```
 
## API
 
### mm.app(options)
 
创建一个 mock 的应用。
 
### mm.cluster(options)
 
创建一个多进程应用,因为是多进程应用,无法获取 worker 的属性,只能通过 supertest 请求。
 
```js
const mm = require('egg-mock');
describe('test/app.js', () => {
  let app, config;
  before(() => {
    app = mm.cluster();
    return app.ready();
  });
  after(() => app.close());
 
  it('some test', () => {
    return app.httpRequest()
      .get('/config')
      .expect(200)
  });
});
```
 
默认会启用覆盖率,因为覆盖率比较慢,可以设置 coverage 关闭
 
```js
mm.cluster({
  coverage: false,
});
```
 
### mm.env(env)
 
设置环境变量,主要用于启动阶段,运行阶段可以使用 app.mockEnv。
 
```js
// 模拟生成环境
mm.env('prod');
mm.app({
  cache: false,
});
```
 
具体值见 <https://github.com/eggjs/egg-core/blob/master/lib/loader/egg_loader.js#L82>
 
### mm.consoleLevel(level)
 
mock 终端日志打印级别
 
```js
// 不输出到终端
mm.consoleLevel('NONE');
```
 
可选 level 为 `DEBUG`, `INFO`, `WARN`, `ERROR`, `NONE`
 
### mm.home(homePath)
 
模拟操作系统用户目录
 
### mm.restore
 
还原所有 mock 数据,一般需要结合 `afterEach(mm.restore)` 使用
 
### options
 
mm.app 和 mm.cluster 的配置参数
 
#### baseDir {String}
 
当前应用的目录,如果是应用本身的测试可以不填默认为 $CWD。
 
指定完整路径
 
```js
mm.app({
  baseDir: path.join(__dirname, 'fixtures/apps/demo'),
})
```
 
也支持缩写,找 test/fixtures 目录下的
 
```js
mm.app({
  baseDir: 'apps/demo',
})
```
 
#### customEgg {String/Boolean}
 
指定框架路径
 
```js
mm.app({
  baseDir: 'apps/demo',
  customEgg: path.join(__dirname, 'fixtures/egg'),
})
```
 
对于框架的测试用例,可以指定 true,会自动加载当前路径。
 
#### plugin
 
指定插件的路径,只用于插件测试。设置为 true 会将当前路径设置到插件列表。
 
```js
mm.app({
  baseDir: 'apps/demo',
  plugin: true,
})
```
 
#### plugins {Object}
 
传入插件列表,可以自定义多个插件
 
#### cache {Boolean}
 
是否需要缓存,默认开启。
 
是通过 baseDir 缓存的,如果不需要可以关闭,但速度会慢。
 
#### clean {Boolean}
 
是否需要清理 log 目录,默认开启。
 
如果是通过 ava 等并行测试框架进行测试,需要手动在执行测试前进行统一的日志清理,不能通过 mm 来处理,设置 `clean` 为 `false`。
 
### app.mockLog([logger]) and app.expectLog(str[, logger])
 
断言指定的字符串记录在指定的日志中。
建议 `app.mockLog()` 和 `app.expectLog()` 配对使用。
单独使用 `app.expectLog()` 需要依赖日志的写入速度,在服务器磁盘高 IO 的时候,会出现不稳定的结果。
 
```js
it('should work', async () => {
  // 将日志记录到内存,用于下面的 expectLog
  app.mockLog();
  await app.httpRequest()
    .get('/')
    .expect('hello world')
    .expect(200);
 
  app.expectLog('foo in logger');
  app.expectLog('foo in coreLogger', 'coreLogger');
  app.expectLog('foo in myCustomLogger', 'myCustomLogger');
});
```
 
### app.httpRequest()
 
请求当前应用 http 服务的辅助工具。
 
```js
it('should work', () => {
  return app.httpRequest()
    .get('/')
    .expect('hello world')
    .expect(200);
});
```
 
更多信息请查看 [supertest](https://github.com/visionmedia/supertest) 的 API 说明。
 
#### .unexpectHeader(name)
 
断言当前请求响应不包含指定 header
 
```js
it('should work', () => {
  return app.httpRequest()
    .get('/')
    .unexpectHeader('set-cookie')
    .expect(200);
});
```
 
#### .expectHeader(name)
 
断言当前请求响应包含指定 header
 
```js
it('should work', () => {
  return app.httpRequest()
    .get('/')
    .expectHeader('set-cookie')
    .expect(200);
});
```
 
### app.mockContext(options)
 
模拟上下文数据
 
```js
const ctx = app.mockContext({
  user: {
    name: 'Jason'
  }
});
console.log(ctx.user.name); // Jason
```
 
### app.mockCookies(data)
 
```js
app.mockCookies({
  foo: 'bar'
});
const ctx = app.mockContext();
console.log(ctx.getCookie('foo'));
```
 
### app.mockHeaders(data)
 
模拟请求头
 
### app.mockSession(data)
 
```js
app.mockSession({
  foo: 'bar'
});
const ctx = app.mockContext();
console.log(ctx.session.foo);
```
 
### app.mockService(service, methodName, fn)
 
```js
it('should mock user name', function* () {
  app.mockService('user', 'getName', function* (ctx, methodName, args) {
    return 'popomore';
  });
  const ctx = app.mockContext();
  yield ctx.service.user.getName();
});
```
 
### app.mockServiceError(service, methodName, error)
 
可以模拟一个错误
 
```js
app.mockServiceError('user', 'home', new Error('mock error'));
```
 
### app.mockCsrf()
 
模拟 csrf,不用传递 token
 
```js
app.mockCsrf();
 
return app.httpRequest()
  .post('/login')
  .expect(302);
```
 
### app.mockHttpclient(url, method, data)
 
模拟 httpclient 的请求,例如 `ctx.curl`
 
```js
app.get('/', function*() {
  const ret = yield this.curl('https://eggjs.org');
  this.body = ret.data.toString();
});
 
app.mockHttpclient('https://eggjs.org', {
  // 模拟的参数,可以是 buffer / string / json,
  // 都会转换成 buffer
  // 按照请求时的 options.dataType 来做对应的转换
  data: 'mock egg',
});
 
return app.httpRequest()
  .post('/')
  .expect('mock egg');
```
 
## Questions & Suggestions
 
Please open an issue [here](https://github.com/eggjs/egg/issues).
 
## License
 
[MIT](LICENSE)