Egg 單元測(cè)試

2020-02-06 14:11 更新

為什么要單元測(cè)試

先問我們自己以下幾個(gè)問題:

  • 你的代碼質(zhì)量如何度量?
  • 你是如何保證代碼質(zhì)量?
  • 你敢隨時(shí)重構(gòu)代碼嗎?
  • 你是如何確保重構(gòu)的代碼依然保持正確性?
  • 你是否有足夠信心在沒有測(cè)試的情況下隨時(shí)發(fā)布你的代碼?

如果答案都比較猶豫,那么就證明我們非常需要單元測(cè)試。

它能帶給我們很多保障:

  • 代碼質(zhì)量持續(xù)有保障
  • 重構(gòu)正確性保障
  • 增強(qiáng)自信心
  • 自動(dòng)化運(yùn)行

Web 應(yīng)用中的單元測(cè)試更加重要,在 Web 產(chǎn)品快速迭代的時(shí)期,每個(gè)測(cè)試用例都給應(yīng)用的穩(wěn)定性提供了一層保障。 API 升級(jí),測(cè)試用例可以很好地檢查代碼是否向下兼容。 對(duì)于各種可能的輸入,一旦測(cè)試覆蓋,都能明確它的輸出。 代碼改動(dòng)后,可以通過測(cè)試結(jié)果判斷代碼的改動(dòng)是否影響已確定的結(jié)果。

所以,應(yīng)用的 Controller、Service、Helper、Extend 等代碼,都必須有對(duì)應(yīng)的單元測(cè)試保證代碼質(zhì)量。 當(dāng)然,框架和插件的每個(gè)功能改動(dòng)和重構(gòu)都需要有相應(yīng)的單元測(cè)試,并且要求盡量做到修改的代碼能被 100% 覆蓋到。

測(cè)試框架

從 npm 搜索『test framework』, 我們會(huì)發(fā)現(xiàn)有大量測(cè)試框架存在,每個(gè)測(cè)試框架都有它的獨(dú)特之處。

Mocha

我們選擇和推薦大家使用 Mocha,功能非常豐富,支持運(yùn)行在 Node.js 和瀏覽器中, 對(duì)異步測(cè)試支持非常友好。

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.

AVA

為什么沒有選擇最近比較火的 AVA,它看起來會(huì)跑得很快。 經(jīng)過我們幾個(gè)真實(shí)項(xiàng)目實(shí)踐下來,AVA 真的只是看起來很美,但是實(shí)際會(huì)讓測(cè)試代碼越來越難寫,成本越來越高。

@dead-horse 的評(píng)價(jià):

AVA 自身不夠穩(wěn)定,并發(fā)運(yùn)行文件多的時(shí)候會(huì)撐爆 CPU;如果設(shè)置控制并發(fā)參數(shù)的方式運(yùn)行,會(huì)導(dǎo)致 only 模式無效。并發(fā)執(zhí)行對(duì)測(cè)試用例的要求很高,所有的測(cè)試不能有依賴,特別是遇到一些需要做 mock 的場(chǎng)景時(shí),寫好很難。app 在初始化的時(shí)候是有耗時(shí)的,如果串行運(yùn)行,只需要初始化一個(gè) app 對(duì)它測(cè)試。 但是 AVA 每一個(gè)文件都運(yùn)行在獨(dú)立進(jìn)程,有多少個(gè)文件就需要初始化多少個(gè) app。

@fool2fish 的評(píng)價(jià):

如果是簡(jiǎn)單的程序的話用 AVA 會(huì)快一些(但是本來就簡(jiǎn)單可能也沒啥感覺), 如果是復(fù)雜的就不推薦了,比較大的問題是可能沒法給出準(zhǔn)確的錯(cuò)誤堆棧, 另外并發(fā)可能會(huì)導(dǎo)致依賴的其他測(cè)試環(huán)境的服務(wù)掛掉,降低測(cè)試的成功率, 還有就是帶流程的測(cè)試(比如測(cè)試數(shù)據(jù)庫的增刪改查功能)真心不適合用 AVA。

斷言庫

同樣,測(cè)試斷言庫也是百花齊放的時(shí)代, 我們經(jīng)歷過 assert,到 should 和 expect,還是不斷地在嘗試更好的斷言庫。

直到我們發(fā)現(xiàn) power-assert, 因?yàn)?a href="http://m.hgci.cn/targetlink?url=https://github.com/atian25/blog/issues/16" target="_blank">『No API is the best API』, 最終我們重新回歸原始的 assert 作為默認(rèn)的斷言庫。

簡(jiǎn)單地說,它的優(yōu)點(diǎn)是:

  • 沒有 API 就是最好的 API,不需要任何記憶,只需 assert 即可。
  • 強(qiáng)大的錯(cuò)誤信息反饋
  • 強(qiáng)大的錯(cuò)誤信息反饋
  • 強(qiáng)大的錯(cuò)誤信息反饋

報(bào)錯(cuò)信息實(shí)在太美太詳細(xì),讓人有種想看錯(cuò)誤報(bào)告的欲望:

測(cè)試約定

為了讓我們更多地關(guān)注測(cè)試用例本身如何編寫,而不是耗費(fèi)時(shí)間在如何運(yùn)行測(cè)試腳本等輔助工作上, 框架對(duì)單元測(cè)試做了一些基本約定。

測(cè)試目錄結(jié)構(gòu)

我們約定 test 目錄為存放所有測(cè)試腳本的目錄,測(cè)試所使用到的 fixtures 和相關(guān)輔助腳本都應(yīng)該放在此目錄下。

測(cè)試腳本文件統(tǒng)一按 ${filename}.test.js 命名,必須以 .test.js 作為文件后綴。

一個(gè)應(yīng)用的測(cè)試目錄示例:

test
├── controller
│   └── home.test.js
├── hello.test.js
└── service
└── user.test.js

測(cè)試運(yùn)行工具

統(tǒng)一使用 egg-bin 來運(yùn)行測(cè)試腳本, 自動(dòng)將內(nèi)置的 Mochaco-mocha、power-assert,nyc 等模塊組合引入到測(cè)試腳本中, 讓我們聚焦精力在編寫測(cè)試代碼上,而不是糾結(jié)選擇那些測(cè)試周邊工具和模塊。

只需要在 package.json 上配置好 scripts.test 即可。

{
"scripts": {
"test": "egg-bin test"
}
}

然后就可以按標(biāo)準(zhǔn)的 npm test 來運(yùn)行測(cè)試了。

npm test

> unittest-example@ test /Users/mk2/git/github.com/eggjs/examples/unittest
> egg-bin test

test/hello.test.js
? should work

1 passing (10ms)

準(zhǔn)備測(cè)試

本文主要介紹如何編寫應(yīng)用的單元測(cè)試,關(guān)于框架和插件的單元測(cè)試請(qǐng)查看框架開發(fā)插件開發(fā)相關(guān)章節(jié)。

mock

正常來說,如果要完整手寫一個(gè) app 創(chuàng)建和啟動(dòng)代碼,還是需要寫一段初始化腳本的, 并且還需要在測(cè)試跑完之后做一些清理工作,如刪除臨時(shí)文件,銷毀 app。

常常還有模擬各種網(wǎng)絡(luò)異常,服務(wù)訪問異常等特殊情況。

所以我們單獨(dú)為框架抽取了一個(gè)測(cè)試 mock 輔助模塊:egg-mock, 有了它我們就可以非??焖俚鼐帉懸粋€(gè) app 的單元測(cè)試,并且還能快速創(chuàng)建一個(gè) ctx 來測(cè)試它的屬性、方法和 Service 等。

app

在測(cè)試運(yùn)行之前,我們首先要?jiǎng)?chuàng)建應(yīng)用的一個(gè) app 實(shí)例, 通過它來訪問需要被測(cè)試的 Controller、Middleware、Service 等應(yīng)用層代碼。

通過 egg-mock,結(jié)合 Mocha 的 before 鉤子就可以便捷地創(chuàng)建出一個(gè) app 實(shí)例。

// test/controller/home.test.js
const assert = require('assert');
const mock = require('egg-mock');

describe('test/controller/home.test.js', () => {
let app;
before(() => {
// 創(chuàng)建當(dāng)前應(yīng)用的 app 實(shí)例
app = mock.app();
// 等待 app 啟動(dòng)成功,才能執(zhí)行測(cè)試用例
return app.ready();
});
});

這樣我們就拿到了一個(gè) app 的引用,接下來所有測(cè)試用例都會(huì)基于這個(gè) app 進(jìn)行。 更多關(guān)于創(chuàng)建 app 的信息請(qǐng)查看 mock.app(options) 文檔。

每一個(gè)測(cè)試文件都需要這樣創(chuàng)建一個(gè) app 實(shí)例非常冗余,因此 egg-mock 提供了一個(gè) bootstrap 文件,可以直接從它上面拿到我們所常用的實(shí)例:

// test/controller/home.test.js
const { app, mock, assert } = require('egg-mock/bootstrap');

describe('test/controller/home.test.js', () => {
// test cases
});

ctx

我們除了 app,還需要一種方式便捷地拿到 ctx,方便我們進(jìn)行 Extend、Service、Helper 等測(cè)試。 而我們已經(jīng)通過上面的方式拿到了一個(gè) app,結(jié)合 egg-mock 提供的 app.mockContext(options) 方法來快速創(chuàng)建一個(gè) ctx 實(shí)例。

it('should get a ctx', () => {
const ctx = app.mockContext();
assert(ctx.method === 'GET');
assert(ctx.url === '/');
});

如果我們想模擬 ctx.user 這個(gè)數(shù)據(jù),也可以通過給 mockContext 傳遞 data 參數(shù)實(shí)現(xiàn):

it('should mock ctx.user', () => {
const ctx = app.mockContext({
user: {
name: 'fengmk2',
},
});
assert(ctx.user);
assert(ctx.user.name === 'fengmk2');
});

現(xiàn)在我們拿到了 app,也知道如何創(chuàng)建一個(gè) ctx 了,那么就可以進(jìn)行更多代碼的單元測(cè)試了。

測(cè)試執(zhí)行順序

特別需要注意的是執(zhí)行順序,盡量保證在執(zhí)行某個(gè)用例的時(shí)候執(zhí)行相關(guān)代碼。

常見的錯(cuò)誤寫法

// Bad
const { app } = require('egg-mock/bootstrap');

describe('bad test', () => {
doSomethingBefore();

it('should redirect', () => {
return app.httpRequest()
.get('/')
.expect(302);
});
});

Mocha 剛開始運(yùn)行的時(shí)候會(huì)載入所有用例,這時(shí) describe 方法就會(huì)被調(diào)用,那 doSomethingBefore 就會(huì)啟動(dòng)。 如果希望使用 only 的方式只執(zhí)行某個(gè)用例那段代碼還是會(huì)被執(zhí)行,這是非預(yù)期的。

正確的做法是將其放到 before 中,只有運(yùn)行這個(gè)套件中某個(gè)用例才會(huì)執(zhí)行。

// Good
const { app } = require('egg-mock/bootstrap');

describe('good test', () => {
before(() => doSomethingBefore());

it('should redirect', () => {
return app.httpRequest()
.get('/')
.expect(302);
});
});

Mocha 使用 before/after/beforeEach/afterEach 來處理前置后置任務(wù),基本能處理所有問題。 每個(gè)用例會(huì)按 before -> beforeEach -> it -> afterEach -> after 的順序執(zhí)行,而且可以定義多個(gè)。

describe('egg test', () => {
before(() => console.log('order 1'));
before(() => console.log('order 2'));
after(() => console.log('order 6'));
beforeEach(() => console.log('order 3'));
afterEach(() => console.log('order 5'));
it('should worker', () => console.log('order 4'));
});

異步測(cè)試

egg-bin 支持測(cè)試異步調(diào)用,它支持多種寫法:

// 使用返回 Promise 的方式
it('should redirect', () => {
return app.httpRequest()
.get('/')
.expect(302);
});

// 使用 callback 的方式
it('should redirect', done => {
app.httpRequest()
.get('/')
.expect(302, done);
});

// 使用 async
it('should redirect', async () => {
await app.httpRequest()
.get('/')
.expect(302);
});

使用哪種寫法取決于不同應(yīng)用場(chǎng)景,如果遇到多個(gè)異步可以使用 async function,也可以拆分成多個(gè)測(cè)試用例。

Controller 測(cè)試

Controller 在整個(gè)應(yīng)用代碼里面屬于比較難測(cè)試的部分了,因?yàn)樗?router 配置緊密相關(guān), 我們需要利用 app.httpRequest() SuperTest 發(fā)起一個(gè)真實(shí)請(qǐng)求, 來將 Router 和 Controller 連接起來,并且可以幫助我們發(fā)送各種滿足邊界條件的請(qǐng)求數(shù)據(jù), 以測(cè)試 Controller 的參數(shù)校驗(yàn)完整性。 app.httpRequest() 是 egg-mock 封裝的 SuperTest 請(qǐng)求實(shí)例。

例如我們要給 app/controller/home.js:

// app/router.js
module.exports = app => {
const { router, controller } = app;
router.get('homepage', '/', controller.home.index);
};

// app/controller/home.js
class HomeController extends Controller {
async index() {
this.ctx.body = 'hello world';
}
}

寫一個(gè)完整的單元測(cè)試,它的測(cè)試代碼 test/controller/home.test.js 如下:

const { app, mock, assert } = require('egg-mock/bootstrap');

describe('test/controller/home.test.js', () => {
describe('GET /', () => {
it('should status 200 and get the body', () => {
// 對(duì) app 發(fā)起 `GET /` 請(qǐng)求
return app.httpRequest()
.get('/')
.expect(200) // 期望返回 status 200
.expect('hello world'); // 期望 body 是 hello world
});

it('should send multi requests', async () => {
// 使用 generator function 方式寫測(cè)試用例,可以在一個(gè)用例中串行發(fā)起多次請(qǐng)求
await app.httpRequest()
.get('/')
.expect(200) // 期望返回 status 200
.expect('hello world'); // 期望 body 是 hello world

// 再請(qǐng)求一次
const result = await app.httpRequest()
.get('/')
.expect(200)
.expect('hello world');

// 也可以這樣驗(yàn)證
assert(result.status === 200);
});
});
});

通過基于 SuperTest 的 app.httpRequest() 可以輕松發(fā)起 GET、POST、PUT 等 HTTP 請(qǐng)求,并且它有非常豐富的請(qǐng)求數(shù)據(jù)構(gòu)造接口, 例如以 POST 方式發(fā)送一個(gè) JSON 請(qǐng)求:

// app/controller/home.js
class HomeController extends Controller {
async post() {
this.ctx.body = this.ctx.request.body;
}
}

// test/controller/home.test.js
it('should status 200 and get the request body', () => {
// 模擬 CSRF token,下文會(huì)詳細(xì)說明
app.mockCsrf();
return app.httpRequest()
.post('/post')
.type('form')
.send({
foo: 'bar',
})
.expect(200)
.expect({
foo: 'bar',
});
});

更詳細(xì)的 HTTP 請(qǐng)求構(gòu)造方式,請(qǐng)查看 SuperTest 文檔。

mock CSRF

框架的默認(rèn)安全插件會(huì)自動(dòng)開啟 CSRF 防護(hù), 如果完整走 CSRF 校驗(yàn)邏輯,那么測(cè)試代碼需要先請(qǐng)求一次頁面,通過解析 HTML 拿到 CSRF token, 然后再使用此 token 發(fā)起 POST 請(qǐng)求。

所以 egg-mock 對(duì) app 增加了 app.mockCsrf() 方法來模擬取 CSRF token 的過程。 這樣在使用 SuperTest 請(qǐng)求 app 就會(huì)自動(dòng)通過 CSRF 校驗(yàn)。

app.mockCsrf();
return app.httpRequest()
.post('/post')
.type('form')
.send({
foo: 'bar',
})
.expect(200)
.expect({
foo: 'bar',
});

Service 測(cè)試

Service 相對(duì)于 Controller 來說,測(cè)試起來會(huì)更加簡(jiǎn)單, 我們只需要先創(chuàng)建一個(gè) ctx,然后通過 ctx.service.${serviceName} 拿到 Service 實(shí)例, 然后調(diào)用 Service 方法即可。

例如

// app/service/user.js
class UserService extends Service {
async get(name) {
return await userDatabase.get(name);
}
}

編寫單元測(cè)試:

describe('get()', () => {
it('should get exists user', async () => {
// 創(chuàng)建 ctx
const ctx = app.mockContext();
// 通過 ctx 訪問到 service.user
const user = await ctx.service.user.get('fengmk2');
assert(user);
assert(user.name === 'fengmk2');
});

it('should get null when user not exists', async () => {
const ctx = app.mockContext();
const user = await ctx.service.user.get('fengmk1');
assert(!user);
});
});

當(dāng)然,實(shí)際的 Service 代碼不會(huì)像我們示例中那么簡(jiǎn)單,這里只是展示如何測(cè)試 Service 而已。

Extend 測(cè)試

應(yīng)用可以對(duì) Application、Request、Response、Context 和 Helper 進(jìn)行擴(kuò)展。 我們可以對(duì)擴(kuò)展的方法或者屬性針對(duì)性的編寫單元測(cè)試。

Application

egg-mock 創(chuàng)建 app 的時(shí)候,已經(jīng)將 Application 的擴(kuò)展自動(dòng)加載到 app 實(shí)例了, 直接使用這個(gè) app 實(shí)例訪問擴(kuò)展的屬性和方法即可進(jìn)行測(cè)試。

例如 app/extend/application.js,我們給 app 增加了一個(gè)基于 ylru 的緩存功能:

const LRU = Symbol('Application#lru');
const LRUCache = require('ylru');
module.exports = {
get lru() {
if (!this[LRU]) {
this[LRU] = new LRUCache(1000);
}
return this[LRU];
},
};

對(duì)應(yīng)的單元測(cè)試:

describe('get lru', () => {
it('should get a lru and it work', () => {
// 設(shè)置緩存
app.lru.set('foo', 'bar');
// 讀取緩存
assert(app.lru.get('foo') === 'bar');
});
});

可以看到,測(cè)試 Application 的擴(kuò)展是最容易的。

Context

Context 測(cè)試只比 Application 多了一個(gè) app.mockContext() 步驟來模擬創(chuàng)建一個(gè) Context 對(duì)象。

例如在 app/extend/context.js 中增加一個(gè) isXHR 屬性,判斷是否通過 XMLHttpRequest 發(fā)起的請(qǐng)求:

module.exports = {
get isXHR() {
return this.get('X-Requested-With') === 'XMLHttpRequest';
},
};

對(duì)應(yīng)的單元測(cè)試:

describe('isXHR()', () => {
it('should true', () => {
const ctx = app.mockContext({
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
});
assert(ctx.isXHR === true);
});

it('should false', () => {
const ctx = app.mockContext({
headers: {
'X-Requested-With': 'SuperAgent',
},
});
assert(ctx.isXHR === false);
});
});

Request

通過 ctx.request 來訪問 Request 擴(kuò)展的屬性和方法,直接即可進(jìn)行測(cè)試。

例如在 app/extend/request.js 中增加一個(gè) isChrome 屬性,判斷是否 Chrome 瀏覽器發(fā)起的請(qǐng)求:

const IS_CHROME = Symbol('Request#isChrome');
module.exports = {
get isChrome() {
if (!this[IS_CHROME]) {
const ua = this.get('User-Agent').toLowerCase();
this[IS_CHROME] = ua.includes('chrome/');
}
return this[IS_CHROME];
},
};

對(duì)應(yīng)的單元測(cè)試:

describe('isChrome()', () => {
it('should true', () => {
const ctx = app.mockContext({
headers: {
'User-Agent': 'Chrome/56.0.2924.51',
},
});
assert(ctx.request.isChrome === true);
});

it('should false', () => {
const ctx = app.mockContext({
headers: {
'User-Agent': 'FireFox/1',
},
});
assert(ctx.request.isChrome === false);
});
});

Response

Response 測(cè)試與 Request 完全一致。 通過 ctx.response 來訪問 Response 擴(kuò)展的屬性和方法,直接即可進(jìn)行測(cè)試。

例如在 app/extend/response.js 中增加一個(gè) isSuccess 屬性,判斷當(dāng)前響應(yīng)狀態(tài)碼是否 200:

module.exports = {
get isSuccess() {
return this.status === 200;
},
};

對(duì)應(yīng)的單元測(cè)試:

describe('isSuccess()', () => {
it('should true', () => {
const ctx = app.mockContext();
ctx.status = 200;
assert(ctx.response.isSuccess === true);
});

it('should false', () => {
const ctx = app.mockContext();
ctx.status = 404;
assert(ctx.response.isSuccess === false);
});
});

Helper

Helper 測(cè)試方式與 Service 類似,也是通過 ctx 來訪問到 Helper,然后調(diào)用 Helper 方法測(cè)試。

例如 app/extend/helper.js

module.exports = {
money(val) {
const lang = this.ctx.get('Accept-Language');
if (lang.includes('zh-CN')) {
return `¥ ${val}`;
}
return `$ ${val}`;
},
};

對(duì)應(yīng)的單元測(cè)試:

describe('money()', () => {
it('should RMB', () => {
const ctx = app.mockContext({
// 模擬 ctx 的 headers
headers: {
'Accept-Language': 'zh-CN,zh;q=0.5',
},
});
assert(ctx.helper.money(100) === '¥ 100');
});

it('should US Dolar', () => {
const ctx = app.mockContext();
assert(ctx.helper.money(100) === '$ 100');
});
});

Mock 方法

egg-mock 除了上面介紹過的 app.mockContext() 和 app.mockCsrf() 方法外,還提供了非常多的 mock 方法幫助我們便捷地寫單元測(cè)試。

  • 如我們不想在終端 console 輸出任何日志,可以通過 mock.consoleLevel('NONE') 來模擬。
  • 又如我想模擬一次請(qǐng)求的 Session 數(shù)據(jù),可以通過 app.mockSession(data) 來模擬。describe('GET /session', () => { it('should mock session work', () => { app.mockSession({ foo: 'bar', uid: 123, }); return app.httpRequest() .get('/session') .expect(200) .expect({ session: { foo: 'bar', uid: 123, }, }); });});

因?yàn)?mock 之后會(huì)一直生效,我們需要避免每個(gè)單元測(cè)試用例之間是不能相互 mock 污染的, 所以通常我們都會(huì)在 afterEach 鉤子里面還原掉所有 mock。

describe('some test', () => {
// before hook

afterEach(mock.restore);

// it tests
});

引入 egg-mock/bootstrap 時(shí),會(huì)自動(dòng)在 afterEach 鉤子中還原所有的 mock,不需要在測(cè)試文件中再次編寫。

下面會(huì)詳細(xì)解釋一下 egg-mock 的常見使用場(chǎng)景。

Mock 屬性和方法

因?yàn)?egg-mock 是擴(kuò)展自 mm 模塊, 它包含了 mm 的所有功能,這樣我們就可以非常方便地 mock 任意對(duì)象的屬性和方法了。

Mock 一個(gè)對(duì)象的屬性

mock app.config.baseDir 指向 /tmp/mockapp

mock(app.config, 'baseDir', '/tmp/mockapp');
assert(app.config.baseDir === '/tmp/mockapp');

Mock 一個(gè)對(duì)象的方法

mock fs.readFileSync 返回 hello world

mock(fs, 'readFileSync', filename => {
return 'hello world';
});
assert(fs.readFileSync('foo.txt') === 'hello world');

還有 mock.data(),mock.error() 等更多高級(jí)的 mock 方法, 詳細(xì)使用說明請(qǐng)查看 mm API。

Mock Service

Service 作為框架標(biāo)準(zhǔn)的內(nèi)置對(duì)象,我們提供了便捷的 app.mockService(service, methodName, fn) 模擬 Service 方法返回值。

例如,模擬 app/service/user 中的 get(name) 方法,讓它返回一個(gè)本來不存在的用戶數(shù)據(jù)。

it('should mock fengmk1 exists', () => {
app.mockService('user', 'get', () => {
return {
name: 'fengmk1',
};
});

return app.httpRequest()
.get('/user?name=fengmk1')
.expect(200)
// 返回了原本不存在的用戶信息
.expect({
name: 'fengmk1',
});
});

通過 app.mockServiceError(service, methodName, error) 可以模擬 Service 調(diào)用異常。

例如,模擬 app/service/user 中的 get(name) 方法調(diào)用異常:

it('should mock service error', () => {
app.mockServiceError('user', 'get', 'mock user service error');
return app.httpRequest()
.get('/user?name=fengmk2')
// service 異常,觸發(fā) 500 響應(yīng)
.expect(500)
.expect(/mock user service error/);
});

Mock HttpClient

框架內(nèi)置了 HttpClient,應(yīng)用發(fā)起的對(duì)外 HTTP 請(qǐng)求基本都是通過它來處理。 我們可以通過 app.mockHttpclient(url, method, data) 來 mock 掉 app.curl 和 ctx.curl 方法, 從而實(shí)現(xiàn)各種網(wǎng)絡(luò)異常情況。

例如在 app/controller/home.js 中發(fā)起了一個(gè) curl 請(qǐng)求

class HomeController extends Controller {
async httpclient () {
const res = await this.ctx.curl('https://eggjs.org');
this.ctx.body = res.data.toString();
}
}

需要 mock 它的返回值:

describe('GET /httpclient', () => {
it('should mock httpclient response', () => {
app.mockHttpclient('https://eggjs.org', {
// 模擬的參數(shù),可以是 buffer / string / json,
// 都會(huì)轉(zhuǎn)換成 buffer
// 按照請(qǐng)求時(shí)的 options.dataType 來做對(duì)應(yīng)的轉(zhuǎn)換
data: 'mock eggjs.org response',
});
return app.httpRequest()
.get('/httpclient')
.expect('mock eggjs.org response');
});
});

示例代碼

完整示例代碼可以在 eggjs/exmaples/unittest 找到。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)