UNPKG

14.7 kBMarkdownView Raw
1# egg-mock
2
3[![NPM version][npm-image]][npm-url]
4[![Node.js CI](https://github.com/eggjs/egg-mock/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eggjs/egg-mock/actions/workflows/nodejs.yml)
5[![Test coverage][codecov-image]][codecov-url]
6[![npm download][download-image]][download-url]
7
8[npm-image]: https://img.shields.io/npm/v/egg-mock.svg?style=flat-square
9[npm-url]: https://npmjs.org/package/egg-mock
10[codecov-image]: https://codecov.io/github/eggjs/egg-mock/coverage.svg?branch=master
11[codecov-url]: https://codecov.io/github/eggjs/egg-mock?branch=master
12[download-image]: https://img.shields.io/npm/dm/egg-mock.svg?style=flat-square
13[download-url]: https://npmjs.org/package/egg-mock
14
15Mock library for testing Egg applications, plugins and custom Egg frameworks with ease. `egg-mock` inherits all APIs from [node_modules/mm](https://github.com/node-modules/mm), offering more flexibility.
16
17## Install
18
19```bash
20$ npm i egg-mock --save-dev
21```
22
23## Usage
24
25### Create testcase
26
27Launch a mock server with `mm.app`
28
29```js
30// test/index.test.js
31const path = require('path');
32const mm = require('egg-mock');
33
34describe('some test', () => {
35 let app;
36 before(() => {
37 app = mm.app({
38 baseDir: 'apps/foo'
39 });
40 return app.ready();
41 })
42 after(() => app.close());
43
44 it('should request /', () => {
45 return app.httpRequest()
46 .get('/')
47 .expect(200);
48 });
49});
50```
51
52Retrieve Agent instance through `app.agent` after `mm.app` started.
53
54Using `mm.cluster` launch cluster server, you can use the same API as `mm.app`;
55
56### Test Application
57
58`baseDir` is optional that is `process.cwd()` by default.
59
60```js
61before(() => {
62 app = mm.app();
63 return app.ready();
64});
65```
66
67### Test Framework
68
69framework is optional, it's `node_modules/egg` by default.
70
71```js
72before(() => {
73 app = mm.app({
74 baseDir: 'apps/demo',
75 framework: true,
76 });
77 return app.ready();
78});
79```
80
81### Test Plugin
82
83If `eggPlugin.name` is defined in `package.json`, it's a plugin that will be loaded to plugin list automatically.
84
85```js
86before(() => {
87 app = mm.app({
88 baseDir: 'apps/demo',
89 });
90 return app.ready();
91});
92```
93
94You can also test the plugin in different framework, e.g. test [aliyun-egg](https://github.com/eggjs/aliyun-egg) and framework-b in one plugin.
95
96```js
97describe('aliyun-egg', () => {
98 let app;
99 before(() => {
100 app = mm.app({
101 baseDir: 'apps/demo',
102 framework: path.join(__dirname, 'node_modules/aliyun-egg'),
103 });
104 return app.ready();
105 });
106});
107
108describe('framework-b', () => {
109 let app;
110 before(() => {
111 app = mm.app({
112 baseDir: 'apps/demo',
113 framework: path.join(__dirname, 'node_modules/framework-b'),
114 });
115 return app.ready();
116 });
117});
118```
119
120If it's detected as an plugin, but you don't want it to be, you can use `plugin = false`.
121
122```js
123before(() => {
124 app = mm.app({
125 baseDir: 'apps/demo',
126 plugin: false,
127 });
128 return app.ready();
129});
130```
131
132## API
133
134### mm.app(options)
135
136Create a mock application.
137
138### mm.cluster(options)
139
140Create a mock cluster server, but you can't use API in application, you should test using `supertest`.
141
142```js
143const mm = require('egg-mock');
144describe('test/app.js', () => {
145 let app, config;
146 before(() => {
147 app = mm.cluster();
148 return app.ready();
149 });
150 after(() => app.close());
151
152 it('some test', () => {
153 return app.httpRequest()
154 .get('/config')
155 .expect(200)
156 });
157});
158```
159
160You can disable coverage, because it's slow.
161
162```js
163mm.cluster({
164 coverage: false,
165});
166```
167
168### mm.env(env)
169
170Mock env when starting
171
172```js
173// production environment
174mm.env('prod');
175mm.app({
176 cache: false,
177});
178```
179
180Environment list <https://github.com/eggjs/egg-core/blob/master/lib/loader/egg_loader.js#L82>
181
182### mm.consoleLevel(level)
183
184Mock level that print to stdout/stderr
185
186```js
187// DON'T log to terminal
188mm.consoleLevel('NONE');
189```
190
191level list: `DEBUG`, `INFO`, `WARN`, `ERROR`, `NONE`
192
193### mm.home(homePath)
194
195mock home directory
196
197### mm.restore()
198
199restore all mock data, e.g. `afterEach(mm.restore)`
200
201### options
202
203Options for `mm.app` and `mm.cluster`
204
205#### baseDir {String}
206
207The directory of application, default is `process.cwd()`.
208
209```js
210mm.app({
211 baseDir: path.join(__dirname, 'fixtures/apps/demo'),
212})
213```
214
215You can use a string based on `$CWD/test/fixtures` for short
216
217```js
218mm.app({
219 baseDir: 'apps/demo',
220})
221```
222
223#### framework {String/Boolean}
224
225The directory of framework
226
227```js
228mm.app({
229 baseDir: 'apps/demo',
230 framework: path.join(__dirname, 'fixtures/egg'),
231})
232```
233
234It can be true when test an framework
235
236#### plugin
237
238The directory of plugin, it's detected automatically.
239
240```js
241mm.app({
242 baseDir: 'apps/demo',
243})
244```
245
246#### plugins {Object}
247
248Define a list of plugins
249
250#### cache {Boolean}
251
252Determine whether enable cache. it's cached by baseDir.
253
254#### clean {Boolean}
255
256Clean all logs directory, default is true.
257
258If you are using `ava`, disable it.
259
260### app.mockLog([logger]) and app.expectLog(str[, logger]), app.notExpectLog(str[, logger])
261
262Assert some string value in the logger instance.
263It is recommended to pair `app.mockLog()` with `app.expectLog()` or `app.notExpectLog()`.
264Using `app.expectLog()` or `app.notExpectLog()` alone requires dependency on the write speed of the log. When the server disk is high IO, unstable results will occur.
265
266```js
267it('should work', async () => {
268 app.mockLog();
269 await app.httpRequest()
270 .get('/')
271 .expect('hello world')
272 .expect(200);
273
274 app.expectLog('foo in logger');
275 app.expectLog('foo in coreLogger', 'coreLogger');
276 app.expectLog('foo in myCustomLogger', 'myCustomLogger');
277
278 app.notExpectLog('bar in logger');
279 app.notExpectLog('bar in coreLogger', 'coreLogger');
280 app.notExpectLog('bar in myCustomLogger', 'myCustomLogger');
281});
282```
283
284### app.httpRequest()
285
286Request current app http server.
287
288```js
289it('should work', () => {
290 return app.httpRequest()
291 .get('/')
292 .expect('hello world')
293 .expect(200);
294});
295```
296
297See [supertest](https://github.com/visionmedia/supertest) to get more APIs.
298
299#### .unexpectHeader(name)
300
301Assert current response not contains the specified header
302
303```js
304it('should work', () => {
305 return app.httpRequest()
306 .get('/')
307 .unexpectHeader('set-cookie')
308 .expect(200);
309});
310```
311
312#### .expectHeader(name)
313
314Assert current response contains the specified header
315
316```js
317it('should work', () => {
318 return app.httpRequest()
319 .get('/')
320 .expectHeader('set-cookie')
321 .expect(200);
322});
323```
324
325### app.mockContext(options)
326
327```js
328const ctx = app.mockContext({
329 user: {
330 name: 'Jason'
331 }
332});
333console.log(ctx.user.name); // Jason
334```
335
336### app.mockContextScope(fn, options)
337
338```js
339await app.mockContextScope(async ctx => {
340 console.log(ctx.user.name); // Jason
341}, {
342 user: {
343 name: 'Jason'
344 }
345});
346```
347
348### app.mockCookies(data)
349
350```js
351app.mockCookies({
352 foo: 'bar'
353});
354const ctx = app.mockContext();
355console.log(ctx.getCookie('foo'));
356```
357
358### app.mockHeaders(data)
359
360Mock request header
361
362### app.mockSession(data)
363
364```js
365app.mockSession({
366 foo: 'bar'
367});
368const ctx = app.mockContext();
369console.log(ctx.session.foo);
370```
371
372### app.mockService(service, methodName, fn)
373
374```js
375it('should mock user name', function* () {
376 app.mockService('user', 'getName', function* (ctx, methodName, args) {
377 return 'popomore';
378 });
379 const ctx = app.mockContext();
380 yield ctx.service.user.getName();
381});
382```
383
384### app.mockServiceError(service, methodName, error)
385
386You can mock an error for service
387
388```js
389app.mockServiceError('user', 'home', new Error('mock error'));
390```
391
392### app.mockCsrf()
393
394```js
395app.mockCsrf();
396
397return app.httpRequest()
398 .post('/login')
399 .expect(302);
400```
401
402### app.mockHttpclient(url, method, data)
403
404Mock httpclient request, e.g.: `ctx.curl`
405
406```js
407app.get('/', function*() {
408 const ret = yield this.curl('https://eggjs.org');
409 this.body = ret.data.toString();
410});
411
412app.mockHttpclient('https://eggjs.org', {
413 // can be buffer / string / json / function
414 // will auto convert to buffer
415 // follow options.dataType to convert
416 data: 'mock egg',
417});
418// app.mockHttpclient('https://eggjs.org', 'get', mockResponse); // mock get
419// app.mockHttpclient('https://eggjs.org', [ 'get' , 'head' ], mockResponse); // mock get and head
420// app.mockHttpclient('https://eggjs.org', '*', mockResponse); // mock all methods
421// app.mockHttpclient('https://eggjs.org', mockResponse); // mock all methods by default
422// app.mockHttpclient('https://eggjs.org', 'get', function(url, opt) { return 'xxx' }); // support fn
423
424return app.httpRequest()
425 .post('/')
426 .expect('mock egg');
427```
428
429You can also use Regular Expression for matching url.
430
431```js
432app.mockHttpclient(/\/users\/[a-z]$/i, {
433 data: {
434 name: 'egg',
435 },
436});
437```
438
439You can alse mock agent.httpclient
440
441```js
442app.agent.mockHttpclient('https://eggjs.org', {
443 data: {
444 name: 'egg',
445 },
446});
447```
448
449## Bootstrap
450
451We also provide a bootstrap file for applications' unit test to reduce duplicated code:
452
453```js
454const { app, mock, assert } = require('egg-mock/bootstrap');
455
456describe('test app', () => {
457 it('should request success', () => {
458 // mock data will be restored each case
459 mock.data(app, 'method', { foo: 'bar' });
460 return app.httpRequest()
461 .get('/foo')
462 .expect(res => {
463 assert(!res.headers.foo);
464 })
465 .expect(/bar/);
466 });
467});
468
469describe('test ctx', () => {
470 it('can use ctx', async function() {
471 const res = await this.ctx.service.foo();
472 assert(res === 'foo');
473 });
474});
475```
476
477We inject ctx to every test case, so you can use `app.currentContext` in your test case.
478and the first call of `app.mockContext` will reuse `app.currentContext`.
479
480```js
481const { app, mock, assert } = require('egg-mock/bootstrap');
482
483describe('test ctx', () => {
484 it('should can use ctx', () => {
485 const ctx = app.currentContext;
486 assert(ctx);
487 });
488
489 it('should reuse ctx', () => {
490 const ctx = app.currentContext;
491 // first call will reuse app.currentContext
492 const mockCtx = app.mockContext();
493 assert(ctx === mockCtx);
494 // next call will create a new context
495 // multi call app.mockContext will get wrong context with app.currentContext
496 // so we recommend to use app.mockContextScope
497 const mockCtx2 = app.mockContext();
498 assert(ctx !== mockCtx);
499 });
500});
501```
502
503And if you use mm.app to bootstrap app, you can manually call setGetAppCallback,
504then egg-mock will inject ctx for each test case.
505```js
506// test/.setup.js
507const mm = require('egg-mock');
508const path = require('path');
509before(async function() {
510 const app = this.app = mm.app();
511 mm.setGetAppCallback(() => {
512 return app;
513 });
514 await app.ready();
515});
516
517
518// test/index.test.js
519it('should work', function() {
520 // eslint-disable-next-line no-undef
521 assert(this.app.currentContext);
522});
523```
524
525### env for custom bootstrap
526EGG_BASE_DIR: the base dir of egg app
527EGG_FRAMEWORK: the framework of egg app
528
529## Questions & Suggestions
530
531Please open an issue [here](https://github.com/eggjs/egg/issues).
532
533## License
534
535[MIT](LICENSE)
536
537<!-- GITCONTRIBUTOR_START -->
538
539## Contributors
540
541|[<img src="https://avatars.githubusercontent.com/u/360661?v=4" width="100px;"/><br/><sub><b>popomore</b></sub>](https://github.com/popomore)<br/>|[<img src="https://avatars.githubusercontent.com/u/156269?v=4" width="100px;"/><br/><sub><b>fengmk2</b></sub>](https://github.com/fengmk2)<br/>|[<img src="https://avatars.githubusercontent.com/u/227713?v=4" width="100px;"/><br/><sub><b>atian25</b></sub>](https://github.com/atian25)<br/>|[<img src="https://avatars.githubusercontent.com/u/985607?v=4" width="100px;"/><br/><sub><b>dead-horse</b></sub>](https://github.com/dead-horse)<br/>|[<img src="https://avatars.githubusercontent.com/u/452899?v=4" width="100px;"/><br/><sub><b>shepherdwind</b></sub>](https://github.com/shepherdwind)<br/>|[<img src="https://avatars.githubusercontent.com/u/456108?v=4" width="100px;"/><br/><sub><b>shaoshuai0102</b></sub>](https://github.com/shaoshuai0102)<br/>|
542| :---: | :---: | :---: | :---: | :---: | :---: |
543|[<img src="https://avatars.githubusercontent.com/u/2160731?v=4" width="100px;"/><br/><sub><b>mansonchor</b></sub>](https://github.com/mansonchor)<br/>|[<img src="https://avatars.githubusercontent.com/u/5856440?v=4" width="100px;"/><br/><sub><b>whxaxes</b></sub>](https://github.com/whxaxes)<br/>|[<img src="https://avatars.githubusercontent.com/u/3139237?v=4" width="100px;"/><br/><sub><b>brickyang</b></sub>](https://github.com/brickyang)<br/>|[<img src="https://avatars.githubusercontent.com/u/880513?v=4" width="100px;"/><br/><sub><b>zbinlin</b></sub>](https://github.com/zbinlin)<br/>|[<img src="https://avatars.githubusercontent.com/u/36814673?v=4" width="100px;"/><br/><sub><b>GoodMeowing</b></sub>](https://github.com/GoodMeowing)<br/>|[<img src="https://avatars.githubusercontent.com/u/5243774?v=4" width="100px;"/><br/><sub><b>ngot</b></sub>](https://github.com/ngot)<br/>|
544|[<img src="https://avatars.githubusercontent.com/u/3274850?v=4" width="100px;"/><br/><sub><b>geekdada</b></sub>](https://github.com/geekdada)<br/>|[<img src="https://avatars.githubusercontent.com/u/7784713?v=4" width="100px;"/><br/><sub><b>shinux</b></sub>](https://github.com/shinux)<br/>|[<img src="https://avatars.githubusercontent.com/u/7530656?v=4" width="100px;"/><br/><sub><b>zhang740</b></sub>](https://github.com/zhang740)<br/>|[<img src="https://avatars.githubusercontent.com/u/225856?v=4" width="100px;"/><br/><sub><b>caoer</b></sub>](https://github.com/caoer)<br/>|[<img src="https://avatars.githubusercontent.com/u/7970645?v=4" width="100px;"/><br/><sub><b>lidianhao123</b></sub>](https://github.com/lidianhao123)<br/>|[<img src="https://avatars.githubusercontent.com/u/9961514?v=4" width="100px;"/><br/><sub><b>limerickgds</b></sub>](https://github.com/limerickgds)<br/>|
545[<img src="https://avatars.githubusercontent.com/u/7971415?v=4" width="100px;"/><br/><sub><b>paranoidjk</b></sub>](https://github.com/paranoidjk)<br/>|[<img src="https://avatars.githubusercontent.com/u/1207064?v=4" width="100px;"/><br/><sub><b>gxcsoccer</b></sub>](https://github.com/gxcsoccer)<br/>|[<img src="https://avatars.githubusercontent.com/u/2127199?v=4" width="100px;"/><br/><sub><b>okoala</b></sub>](https://github.com/okoala)<br/>|[<img src="https://avatars.githubusercontent.com/u/10825163?v=4" width="100px;"/><br/><sub><b>ImHype</b></sub>](https://github.com/ImHype)<br/>|[<img src="https://avatars.githubusercontent.com/u/16033313?v=4" width="100px;"/><br/><sub><b>linjiajian999</b></sub>](https://github.com/linjiajian999)<br/>
546
547This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Fri Apr 29 2022 22:49:14 GMT+0800`.
548
549<!-- GITCONTRIBUTOR_END -->