Gathering detailed insights and metrics for egg-mock
Gathering detailed insights and metrics for egg-mock
Gathering detailed insights and metrics for egg-mock
Gathering detailed insights and metrics for egg-mock
npm install egg-mock
Typescript
Module System
Min. Node Version
Node Version
NPM Version
TypeScript (99.67%)
JavaScript (0.33%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
145 Stars
303 Commits
31 Forks
26 Watchers
12 Branches
40 Contributors
Updated on Feb 08, 2025
Latest Version
5.15.1
Package Id
egg-mock@5.15.1
Unpacked Size
99.64 kB
Size
26.19 kB
File Count
35
NPM Version
10.8.2
Node Version
18.20.5
Published on
Dec 12, 2024
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
16
一个数据模拟的库,更方便地测试 Egg 应用、插件及自定义 Egg 框架。egg-mock
拓展自 node_modules/mm,你可以使用所有 mm
包含的 API。
1$ npm i egg-mock --save-dev
通过 mm.app
启动应用,可以使用 App 的 API 模拟数据
1// test/index.test.js 2const path = require('path'); 3const mm = require('egg-mock'); 4 5describe('some test', () => { 6 let app; 7 before(() => { 8 app = mm.app({ 9 baseDir: 'apps/foo' 10 customEgg: path.join(__dirname, '../node_modules/egg'), 11 }); 12 return app.ready(); 13 }) 14 after(() => app.close()); 15 16 it('should request /', () => { 17 return app.httpRequest() 18 .get('/') 19 .expect(200); 20 }); 21});
使用 mm.app
启动后可以通过 app.agent
访问到 agent 对象。
使用 mm.cluster
启动多进程测试,API 与 mm.app
一致。
应用开发者不需要传入 baseDir,其为当前路径
1before(() => { 2 app = mm.app({ 3 customEgg: path.join(__dirname, '../node_modules/egg'), 4 }); 5 return app.ready(); 6});
框架开发者需要指定 customEgg,会将当前路径指定为框架入口
1before(() => { 2 app = mm.app({ 3 baseDir: 'apps/demo', 4 customEgg: true, 5 }); 6 return app.ready(); 7});
在插件目录下执行测试用例时,只要 package.json
中有 eggPlugin.name
字段,就会自动把当前目录加到插件列表中。
1before(() => { 2 app = mm.app({ 3 baseDir: 'apps/demo', 4 customEgg: path.join(__dirname, '../node_modules/egg'), 5 }); 6 return app.ready(); 7});
也可以通过 customEgg 指定其他框架,比如希望在 aliyun-egg 和 framework-b 同时测试此插件。
1describe('aliyun-egg', () => { 2 let app; 3 before(() => { 4 app = mm.app({ 5 baseDir: 'apps/demo', 6 customEgg: path.join(__dirname, 'node_modules/aliyun-egg'), 7 }); 8 return app.ready(); 9 }); 10}); 11 12describe('framework-b', () => { 13 let app; 14 before(() => { 15 app = mm.app({ 16 baseDir: 'apps/demo', 17 customEgg: path.join(__dirname, 'node_modules/framework-b'), 18 }); 19 return app.ready(); 20 }); 21});
如果当前目录确实是一个 egg 插件,但是又不想当它是一个插件来测试,可以通过 options.plugin
选项来关闭:
1before(() => { 2 app = mm.app({ 3 baseDir: 'apps/demo', 4 customEgg: path.join(__dirname, 'node_modules/egg'), 5 plugin: false, 6 }); 7 return app.ready(); 8});
创建一个 mock 的应用。
创建一个多进程应用,因为是多进程应用,无法获取 worker 的属性,只能通过 supertest 请求。
1const mm = require('egg-mock'); 2describe('test/app.js', () => { 3 let app, config; 4 before(() => { 5 app = mm.cluster(); 6 return app.ready(); 7 }); 8 after(() => app.close()); 9 10 it('some test', () => { 11 return app.httpRequest() 12 .get('/config') 13 .expect(200) 14 }); 15});
默认会启用覆盖率,因为覆盖率比较慢,可以设置 coverage 关闭
1mm.cluster({ 2 coverage: false, 3});
设置环境变量,主要用于启动阶段,运行阶段可以使用 app.mockEnv。
1// 模拟生成环境 2mm.env('prod'); 3mm.app({ 4 cache: false, 5});
具体值见 https://github.com/eggjs/egg-core/blob/master/lib/loader/egg_loader.js#L82
mock 终端日志打印级别
1// 不输出到终端 2mm.consoleLevel('NONE');
可选 level 为 DEBUG
, INFO
, WARN
, ERROR
, NONE
模拟操作系统用户目录
还原所有 mock 数据,一般需要结合 afterEach(mm.restore)
使用
mm.app 和 mm.cluster 的配置参数
当前应用的目录,如果是应用本身的测试可以不填默认为 $CWD。
指定完整路径
1mm.app({ 2 baseDir: path.join(__dirname, 'fixtures/apps/demo'), 3})
也支持缩写,找 test/fixtures 目录下的
1mm.app({ 2 baseDir: 'apps/demo', 3})
指定框架路径
1mm.app({ 2 baseDir: 'apps/demo', 3 customEgg: path.join(__dirname, 'fixtures/egg'), 4})
对于框架的测试用例,可以指定 true,会自动加载当前路径。
指定插件的路径,只用于插件测试。设置为 true 会将当前路径设置到插件列表。
1mm.app({ 2 baseDir: 'apps/demo', 3 plugin: true, 4})
传入插件列表,可以自定义多个插件
是否需要缓存,默认开启。
是通过 baseDir 缓存的,如果不需要可以关闭,但速度会慢。
是否需要清理 log 目录,默认开启。
如果是通过 ava 等并行测试框架进行测试,需要手动在执行测试前进行统一的日志清理,不能通过 mm 来处理,设置 clean
为 false
。
断言指定的字符串记录在指定的日志中。
建议 app.mockLog()
和 app.expectLog()
或者 app.notExpectLog()
配对使用。
单独使用 app.expectLog()
或者 app.notExpectLog()
需要依赖日志的写入速度,在服务器磁盘高 IO 的时候,会出现不稳定的结果。
1it('should work', async () => { 2 // 将日志记录到内存,用于下面的 expectLog 3 app.mockLog(); 4 await app.httpRequest() 5 .get('/') 6 .expect('hello world') 7 .expect(200); 8 9 app.expectLog('foo in logger'); 10 app.expectLog('foo in coreLogger', 'coreLogger'); 11 app.expectLog('foo in myCustomLogger', 'myCustomLogger'); 12 13 app.notExpectLog('bar in logger'); 14 app.notExpectLog('bar in coreLogger', 'coreLogger'); 15 app.notExpectLog('bar in myCustomLogger', 'myCustomLogger'); 16});
请求当前应用 http 服务的辅助工具。
1it('should work', () => { 2 return app.httpRequest() 3 .get('/') 4 .expect('hello world') 5 .expect(200); 6});
更多信息请查看 supertest 的 API 说明。
断言当前请求响应不包含指定 header
1it('should work', () => { 2 return app.httpRequest() 3 .get('/') 4 .unexpectHeader('set-cookie') 5 .expect(200); 6});
断言当前请求响应包含指定 header
1it('should work', () => { 2 return app.httpRequest() 3 .get('/') 4 .expectHeader('set-cookie') 5 .expect(200); 6});
模拟上下文数据
1const ctx = app.mockContext({ 2 user: { 3 name: 'Jason' 4 } 5}); 6console.log(ctx.user.name); // Jason
安全的模拟上下文数据,同一用例用多次调用 mockContext 可能会造成 AsyncLocalStorage 污染
1await app.mockContextScope(async ctx => {
2 console.log(ctx.user.name); // Jason
3}, {
4 user: {
5 name: 'Jason'
6 }
7});
1app.mockCookies({ 2 foo: 'bar' 3}); 4const ctx = app.mockContext(); 5console.log(ctx.getCookie('foo'));
模拟请求头
1app.mockSession({ 2 foo: 'bar' 3}); 4const ctx = app.mockContext(); 5console.log(ctx.session.foo);
1it('should mock user name', function* () { 2 app.mockService('user', 'getName', function* (ctx, methodName, args) { 3 return 'popomore'; 4 }); 5 const ctx = app.mockContext(); 6 yield ctx.service.user.getName(); 7});
可以模拟一个错误
1app.mockServiceError('user', 'home', new Error('mock error'));
模拟 csrf,不用传递 token
1app.mockCsrf(); 2 3return app.httpRequest() 4 .post('/login') 5 .expect(302);
模拟 httpclient 的请求,例如 ctx.curl
1app.get('/', async ctx => {
2 const ret = await ctx.curl('https://eggjs.org');
3 this.body = ret.data.toString();
4});
5
6app.mockHttpclient('https://eggjs.org', {
7 // 模拟的参数,可以是 buffer / string / json / function
8 // 都会转换成 buffer
9 // 按照请求时的 options.dataType 来做对应的转换
10 data: 'mock egg',
11});
12
13return app.httpRequest()
14 .post('/')
15 .expect('mock egg');
我们提供了一个 bootstrap 来减少单测中的重复代码:
1const { app, mock, assert } = require('egg-mock/bootstrap'); 2 3describe('test app', () => { 4 it('should request success', () => { 5 // mock data will be restored each case 6 mock.data(app, 'method', { foo: 'bar' }); 7 return app.httpRequest() 8 .get('/foo') 9 .expect(res => { 10 assert(!res.headers.foo); 11 }) 12 .expect(/bar/); 13 }); 14}); 15 16describe('test ctx', () => { 17 it('can use ctx', async function() { 18 const res = await this.ctx.service.foo(); 19 assert(res === 'foo'); 20 }); 21});
我们将会在每个 case 中自定注入 ctx, 可以通过 app.currentContext
来获取当前的 ctx。
并且第一次使用 app.mockContext
会自动复用当前 case 的上下文。
1const { app, mock, assert } = require('egg-mock/bootstrap'); 2 3describe('test ctx', () => { 4 it('should can use ctx', () => { 5 const ctx = app.currentContext; 6 assert(ctx); 7 }); 8 9 it('should reuse ctx', () => { 10 const ctx = app.currentContext; 11 // 第一次调用会复用上下文 12 const mockCtx = app.mockContext(); 13 assert(ctx === mockCtx); 14 // 后续调用会新建上下文 15 // 极不建议多次调用 app.mockContext 16 // 这会导致上下文污染 17 // 建议使用 app.mockContextScope 18 const mockCtx2 = app.mockContext(); 19 assert(ctx !== mockCtx); 20 }); 21});
EGG_BASE_DIR: the base dir of egg app EGG_FRAMEWORK: the framework of egg app
Please open an issue here.
Made with contributors-img.
No vulnerabilities found.
No security vulnerabilities found.