From cf3de0f248e3435a7d6ac41ece16dea55f5e86c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?TZ=20=7C=20=E5=A4=A9=E7=8C=AA?= Date: Wed, 29 Nov 2017 08:51:39 +0800 Subject: [PATCH] docs(unittest): use async (#1716) --- app/extend/context.js | 4 +- docs/source/en/core/unittest.md | 116 +++++++++++++++++------------ docs/source/zh-cn/core/unittest.md | 88 ++++++++++++---------- index.d.ts | 2 +- lib/egg.js | 2 +- 5 files changed, 118 insertions(+), 94 deletions(-) diff --git a/app/extend/context.js b/app/extend/context.js index 6e1f95b41d..c1c27f9256 100644 --- a/app/extend/context.js +++ b/app/extend/context.js @@ -188,8 +188,8 @@ const proto = module.exports = { * this.body = 'hi'; * * this.runInBackground(async ctx => { - * yield ctx.mysql.query(sql); - * yield ctx.curl(url); + * await ctx.mysql.query(sql); + * await ctx.curl(url); * }); * ``` */ diff --git a/docs/source/en/core/unittest.md b/docs/source/en/core/unittest.md index d89ab71b44..c32f8069b6 100644 --- a/docs/source/en/core/unittest.md +++ b/docs/source/en/core/unittest.md @@ -33,8 +33,6 @@ Mocha is our first choice. > Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases. -And it's more efficient to use together with another [co-mocha](https://npmjs.com/co-mocha) module, which provides many advanced features, such as generator function, async and await. - ### AVA Why not another recently popular framework [AVA](https://github.com/avajs/ava) which looks like faster? AVA is great, but practice of serveral projects tells us the truth that code is harder to write. @@ -52,7 +50,7 @@ Comments from [@fool2fish](https://github.com/fool2fish): [Assertion libraries](https://www.npmjs.com/search?q=assert&page=1&ranking=popularity), as flourishing as test frameworks, are emerged continuously. The one we used has changed from [assert](https://nodejs.org/api/assert.html) to [should](https://github.com/shouldjs/should.js), and then to [expect](https://github.com/Automattic/expect.js) , but we are still trying to find better one. -In the end, we go back to the original assertion library because of the appearance of [power-assert](https://github.com/power-assert-js/power-assert), which best expresses [『No API is the best API』](https://github.com/atian25/blog/issues/16). +In the end, we go back to the original assertion library because of the appearance of [power-assert], which best expresses [『No API is the best API』](https://github.com/atian25/blog/issues/16). To be Short, Here are it's advantages: - No API is the best API. Assert is all. @@ -86,7 +84,7 @@ test ### Test Tool -Consistently using [egg-bin to launch tests](./development.md#unit_testing) , which automaticlly load modules like mocha, co-mocha, power-assert, istanbul into test scripts, so that we can ** concentrate on writing tests ** without wasting time on the choice of various test tools or modules. +Consistently using [egg-bin to launch tests](./development.md#unit_testing) , which automaticlly load modules like [Mocha], [co-mocha], [power-assert], [nyc] into test scripts, so that we can **concentrate on writing tests** without wasting time on the choice of various test tools or modules. The only thing you need to do is setting `scripts.test` in `package.json`. @@ -157,7 +155,7 @@ describe('test/controller/home.test.js', () => { }); ``` -### ctx +### ctx Except app, tests for Extend, Service and Helper are also taken into consideration. Let's ceate a context through [`app.mockContext(options)`](https://github.com/eggjs/egg-mock#appmockcontextoptions) offered by egg-mock. @@ -189,7 +187,7 @@ Since we have got the app and the context, you are free to do a lot of tests. Pay close attention to testing order, and make sure any chunk of code is executed as you expected. -Common Error: +Common Error: ```js // Bad @@ -229,10 +227,10 @@ Mocha have keywords - before, after, beforeEach and afterEach - to set up precon ## Asynchronous Test -egg-bin is going to load co-mocha module automaticlly to support asynchronous test, no matter how to implement, such as Promise returned by `app.httpRequest` mentioned above. +egg-bin support asynchronous test: ```js -// return Promise +// using Promise it('should redirect', () => { return app.httpRequest() .get('/') @@ -246,15 +244,15 @@ it('should redirect', done => { .expect(302, done); }); -// return generator -it('should redirect', function* () { - yield app.httpRequest() +// using async +it('should redirect', async () => { + await app.httpRequest() .get('/') .expect(302); }); ``` -According to specific situation, you could make different choice of these ways. Multiple asynchronous test cases could be composed to one test, or divided into several independent tests. +According to specific situation, you could make different choice of these ways. Multiple asynchronous test cases could be composed to one test with async function, or divided into several independent tests. ## Controller Test @@ -265,13 +263,16 @@ Here is an `app/controller/home.js` example. ```js // app/router.js module.exports = app => { - app.get('homepage', '/', 'home.index'); + const { router, controller } = app; + router.get('homepage', '/', controller.home.index); }; // app/controller/home.js -exports.index = function* (ctx) { - ctx.body = 'hello world'; -}; +class HomeController extends Controler { + async index() { + this.ctx.body = 'hello world'; + } +} ``` Then a test. @@ -290,15 +291,14 @@ describe('test/controller/home.test.js', () => { .expect('hello world'); // set expectaion of body to 'hello world' }); - it('should send multi requests', function* () { - // one test could contains multiple request test cases that were yield - yield app.httpRequest() + it('should send multi requests', async () => { + await app.httpRequest() .get('/') .expect(200) v .expect('hello world'); // set expectaion of body to 'hello world' // once more - const result = yield app.httpRequest() + const result = await app.httpRequest() .get('/') .expect(200) .expect('hello world'); @@ -314,9 +314,11 @@ describe('test/controller/home.test.js', () => { ```js // app/controller/home.js -exports.post = function* (ctx) { - ctx.body = ctx.request.body; -}; +class HomeController extends Controler { + async post() { + this.ctx.body = this.ctx.request.body; + } +} // test/controller/home.test.js it('should status 200 and get the request body', () => { @@ -359,35 +361,34 @@ return app.httpRequest() Service is easier to test than Controller. Creating a ctx, and then get the instance of Service via `ctx.service.${serviceName}`, and then use the instance to test. -For example, `app/service/user.js`: +For example: ```js -module.exports = app => { - return class User extends app.Service { - * get(name) { - return yield userDatabase.get(name); - } - }; -}; +// app/service/user.js +class UserService extends Service { + async get(name) { + return await userDatabase.get(name); + } +} ``` -And a test: +And a test: ```js describe('get()', () => { // using generator function because of asynchronous invoking - it('should get exists user', function* () { + it('should get exists user', async () => { // create ctx const ctx = app.mockContext(); // get service.user via ctx - const user = yield ctx.service.user.get('fengmk2'); + const user = await ctx.service.user.get('fengmk2'); assert(user); assert(user.name === 'fengmk2'); }); - it('should get null when user not exists', function* () { + it('should get null when user not exists', async () => { const ctx = app.mockContext(); - const user = yield ctx.service.user.get('fengmk1'); + const user = await ctx.service.user.get('fengmk1'); assert(!user); }); }); @@ -418,7 +419,7 @@ module.exports = { }; ``` -A corresponding test: +A corresponding test: ```js describe('get lru', () => { @@ -447,7 +448,7 @@ module.exports = { }; ``` -A corresponding test: +A corresponding test: ```js describe('isXHR()', () => { @@ -490,7 +491,7 @@ module.exports = { }; ``` -A corresponding test: +A corresponding test: ```js describe('isChrome()', () => { @@ -528,7 +529,7 @@ module.exports = { }; ``` -The corresponding test: +The corresponding test: ```js describe('isSuccess()', () => { @@ -564,7 +565,7 @@ module.exports = { }; ``` -A corresponding test: +A corresponding test: ```js describe('money()', () => { @@ -585,7 +586,7 @@ describe('money()', () => { }); ``` -## Mock Function +## Mock Function Except functions mentioned above, like `app.mockContext()` and `app.mockCsrf()`, egg-mock provides [quite a few mocking functions](https://github.com/eggjs/egg-mock#api) to make writing tests easier. @@ -614,7 +615,17 @@ describe('GET /session', () => { Remember to restore mock data in an `afterEach` hook, otherwise it would take effect with all the tests that supposed to be independent to each other. -**When you required `egg-mock/bootstrap`, resetting work would be done automaticly in an `afterEach` hook. Do not write these code any more.** +```js +describe('some test', () => { + // before hook + + afterEach(mock.restore); + + // it tests +}); +``` + +**When you use `egg-mock/bootstrap`, resetting work would be done automaticly in an `afterEach` hook. Do need to write these code any more.** The following will describe the common usage of egg-mock. @@ -652,7 +663,7 @@ For example, mock the method `get(name)` in `app/service/user` to return a nonex ```js it('should mock fengmk1 exists', () => { - app.mockService('user', 'get', function* () { + app.mockService('user', 'get', () => { return { name: 'fengmk1', }; @@ -690,10 +701,12 @@ External HTTP requests should be performed though [HttpClient](./httpclient.md), For example, we submit a request in `app/controller/home.js`. ```js -exports.httpclient = function* (ctx) { - const res = ctx.curl('https://eggjs.org'); - ctx.body = res.data.toString(); -}; +class HomeController extends Controller { + async httpclient () { + const res = await this.ctx.curl('https://eggjs.org'); + this.ctx.body = res.data.toString(); + } +} ``` Then mock it's response. @@ -716,4 +729,9 @@ describe('GET /httpclient', () => { ## Sample Code -All sample code can be found in [eggjs/exmaples/unittest](https://github.com/eggjs/examples/blob/master/unittest) \ No newline at end of file +All sample code can be found in [eggjs/exmaples/unittest](https://github.com/eggjs/examples/blob/master/unittest) + +[Mocha]: https://mochajs.org +[co-mocha]: https://github.com/blakeembrey/co-mocha +[nyc]: https://github.com/istanbuljs/nyc +[power-assert]: https://github.com/power-assert-js/power-assert \ No newline at end of file diff --git a/docs/source/zh-cn/core/unittest.md b/docs/source/zh-cn/core/unittest.md index 998d6d9f0c..eef8b9e3ee 100644 --- a/docs/source/zh-cn/core/unittest.md +++ b/docs/source/zh-cn/core/unittest.md @@ -39,9 +39,6 @@ API 升级,测试用例可以很好地检查代码是否向下兼容。 > Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases. -加上 [co-mocha](https://npmjs.com/co-mocha) 模块的帮助, -扩展了 Mocha 的多种用例书写方式,例如 generator function,async await 等。 - ### AVA 为什么没有选择最近比较火的 [AVA](https://github.com/avajs/ava),它看起来会跑得很快。 @@ -64,7 +61,7 @@ API 升级,测试用例可以很好地检查代码是否向下兼容。 同样,测试断言库也是[百花齐放的时代](https://www.npmjs.com/search?q=assert&page=1&ranking=popularity), 我们经历过 [assert](https://nodejs.org/api/assert.html),到 [should](https://github.com/shouldjs/should.js) 和 [expect](https://github.com/Automattic/expect.js),还是不断地在尝试更好的断言库。 -直到我们发现 [power-assert](https://github.com/power-assert-js/power-assert), +直到我们发现 [power-assert], 因为[『No API is the best API』](https://github.com/atian25/blog/issues/16), 最终我们重新回归原始的 assert 作为默认的断言库。 @@ -103,7 +100,7 @@ test ### 测试运行工具 统一使用 [egg-bin 来运行测试脚本](./development.md#单元测试), -自动将内置的 Mocha、co-mocha、power-assert,istanbul 等模块组合引入到测试脚本中, +自动将内置的 [Mocha]、[co-mocha]、[power-assert],[nyc] 等模块组合引入到测试脚本中, 让我们**聚焦精力在编写测试代码**上,而不是纠结选择那些测试周边工具和模块。 只需要在 `package.json` 上配置好 `scripts.test` 即可。 @@ -231,8 +228,7 @@ describe('bad test', () => { }); ``` -Mocha 刚开始运行的时候会载入所有用例,这时 describe 方法就会被调用, -那 `doSomethingBefore` 就会启动。 +Mocha 刚开始运行的时候会载入所有用例,这时 describe 方法就会被调用,那 `doSomethingBefore` 就会启动。 如果希望使用 only 的方式只执行某个用例那段代码还是会被执行,这是非预期的。 正确的做法是将其放到 before 中,只有运行这个套件中某个用例才会执行。 @@ -268,7 +264,7 @@ describe('egg test', () => { ## 异步测试 -egg-bin 会自动加载 co-mocha 插件测试异步调用,它支持多种写法,比如上面 `app.httpRequest` 方法支持返回 Promise: +egg-bin 支持测试异步调用,它支持多种写法: ```js // 使用返回 Promise 的方式 @@ -285,15 +281,15 @@ it('should redirect', done => { .expect(302, done); }); -// 使用 generator -it('should redirect', function* () { - yield app.httpRequest() +// 使用 async +it('should redirect', async () => { + await app.httpRequest() .get('/') .expect(302); }); ``` -使用哪种写法取决于不同应用场景,如果遇到多个异步可以使用 generator function,也可以拆分成多个测试用例。 +使用哪种写法取决于不同应用场景,如果遇到多个异步可以使用 async function,也可以拆分成多个测试用例。 ## Controller 测试 @@ -307,13 +303,16 @@ Controller 在整个应用代码里面属于比较难测试的部分了,因为 ```js // app/router.js module.exports = app => { - app.get('homepage', '/', 'home.index'); + const { router, controller } = app; + router.get('homepage', '/', controller.home.index); }; // app/controller/home.js -exports.index = function* (ctx) { - ctx.body = 'hello world'; -}; +class HomeController extends Controler { + async index() { + this.ctx.body = 'hello world'; + } +} ``` 写一个完整的单元测试,它的测试代码 `test/controller/home.test.js` 如下: @@ -331,15 +330,15 @@ describe('test/controller/home.test.js', () => { .expect('hello world'); // 期望 body 是 hello world }); - it('should send multi requests', function* () { + it('should send multi requests', async () => { // 使用 generator function 方式写测试用例,可以在一个用例中串行发起多次请求 - yield app.httpRequest() + await app.httpRequest() .get('/') .expect(200) // 期望返回 status 200 .expect('hello world'); // 期望 body 是 hello world // 再请求一次 - const result = yield app.httpRequest() + const result = await app.httpRequest() .get('/') .expect(200) .expect('hello world'); @@ -356,9 +355,11 @@ describe('test/controller/home.test.js', () => { ```js // app/controller/home.js -exports.post = function* (ctx) { - ctx.body = ctx.request.body; -}; +class HomeController extends Controler { + async post() { + this.ctx.body = this.ctx.request.body; + } +} // test/controller/home.test.js it('should status 200 and get the request body', () => { @@ -408,35 +409,33 @@ Service 相对于 Controller 来说,测试起来会更加简单, 我们只需要先创建一个 ctx,然后通过 `ctx.service.${serviceName}` 拿到 Service 实例, 然后调用 Service 方法即可。 -例如给 `app/service/user.js` +例如 ```js -module.exports = app => { - return class User extends app.Service { - * get(name) { - return yield userDatabase.get(name); - } - }; -}; +// app/service/user.js +class UserService extends Service { + async get(name) { + return await userDatabase.get(name); + } +} ``` 编写单元测试: ```js describe('get()', () => { - // 因为需要异步调用,所以使用 generator function - it('should get exists user', function* () { + it('should get exists user', async () => { // 创建 ctx const ctx = app.mockContext(); // 通过 ctx 访问到 service.user - const user = yield ctx.service.user.get('fengmk2'); + const user = await ctx.service.user.get('fengmk2'); assert(user); assert(user.name === 'fengmk2'); }); - it('should get null when user not exists', function* () { + it('should get null when user not exists', async () => { const ctx = app.mockContext(); - const user = yield ctx.service.user.get('fengmk1'); + const user = await ctx.service.user.get('fengmk1'); assert(!user); }); }); @@ -668,7 +667,7 @@ egg-mock 除了上面介绍过的 `app.mockContext()` 和 `app.mockCsrf()` 方 所以通常我们都会在 `afterEach` 钩子里面还原掉所有 mock。 ```js -describe('some tes', () => { +describe('some test', () => { // before hook afterEach(mock.restore); @@ -717,7 +716,7 @@ Service 作为框架标准的内置对象,我们提供了便捷的 `app.mockSe ```js it('should mock fengmk1 exists', () => { - app.mockService('user', 'get', function* () { + app.mockService('user', 'get', () => { return { name: 'fengmk1', }; @@ -757,10 +756,12 @@ it('should mock service error', () => { 例如在 `app/controller/home.js` 中发起了一个 curl 请求 ```js -exports.httpclient = function* (ctx) { - const res = ctx.curl('https://eggjs.org'); - ctx.body = res.data.toString(); -}; +class HomeController extends Controller { + async httpclient () { + const res = await this.ctx.curl('https://eggjs.org'); + this.ctx.body = res.data.toString(); + } +} ``` 需要 mock 它的返回值: @@ -784,3 +785,8 @@ describe('GET /httpclient', () => { ## 示例代码 完整示例代码可以在 [eggjs/exmaples/unittest](https://github.com/eggjs/examples/blob/master/unittest) 找到。 + +[Mocha]: https://mochajs.org +[co-mocha]: https://github.com/blakeembrey/co-mocha +[nyc]: https://github.com/istanbuljs/nyc +[power-assert]: https://github.com/power-assert-js/power-assert diff --git a/index.d.ts b/index.d.ts index bc63782d3a..6571eb3631 100644 --- a/index.d.ts +++ b/index.d.ts @@ -758,7 +758,7 @@ export interface Context extends KoaApplication.Context { * get upload file stream * @example * ```js - * const stream = yield this.getFileStream(); + * const stream = await this.getFileStream(); * // get other fields * console.log(stream.fields); * ``` diff --git a/lib/egg.js b/lib/egg.js index 02b790f077..ca1d1e1fd0 100644 --- a/lib/egg.js +++ b/lib/egg.js @@ -228,7 +228,7 @@ class EggApplication extends EggCore { * * @example * ```js - * const result = yield app.curl('http://example.com/foo.json', { + * const result = await app.curl('http://example.com/foo.json', { * method: 'GET', * dataType: 'json', * });