folder_open

30 天打造 MERN Stack Boilerplate

arrow_right
article

Day 22 - Testing - 撰寫 End-To-End API 測試

Day 22 - Testing - 撰寫 End-To-End API 測試

各位是否還記得在 Day 12 - Infrastructure - Isomorphic API 中,我們提出了 API 特殊寫法與用法,事實上這樣的寫法除了滿足 Isomorphism 之外,它還是一種可測試(Testable)的寫法,請見本文說明。

Flow Part

#

從流程的觀點來看,我們要先描述目前要測試哪一個 API,還有要測試該 API 的哪些 Methods,例如我想測試 todoAPI 的 create() 和 list():

describe('#todoAPI', () => {
describe('#create()', () => {
// ...
});
describe('#list()', () => {
// ...
});
});

在測試過程中我們會動到測試資料庫,所以開始測試的前後,我慣例上會先清空所有資料:

import Todo from '../../../build/server/models/Todo';
describe('#todoAPI', () => {
before((done) => {
Todo.remove({}, done);
});
describe('#create()', () => {
// ...
});
describe('#list()', () => {
// ...
});
after((done) => {
Todo.remove({}, done);
});
});

然後再 Import 待測試的 API,準備好假資料,照著平常呼叫 API 的寫法來使用即可:

import { apiEngine } from '../../utils';
import todoAPI from '../../../build/common/api/todo';
import async from 'async';
import Todo from '../../../build/server/models/Todo';
describe('#todoAPI', () => {
let fakeTodos = [{
text: 'this is a fake todo text',
}, {
text: 'foo',
}, {
text: '~bar~',
}];
before((done) => {
Todo.remove({}, done);
});
describe('#create()', () => {
it('should create todo', (done) => {
async.eachSeries(fakeTodos, (fakeTodo, cb) => {
todoAPI(apiEngine)
.create(fakeTodo)
.then((json) => {
// ...
cb();
});
}, done);
});
});
describe('#list()', () => {
it('should list todos', (done) => {
todoAPI(apiEngine)
.list({ page: 1 })
.then((json) => {
// ...
done();
});
});
});
after((done) => {
Todo.remove({}, done);
});
});

由於測試資料可能會有很多筆,所以這裡還使用到了 open_in_new 這套 Library,輔助我們在非同步的環境下按照順序走過全部的測資。

Assertion Part

#

API 本身的錯誤已經被包裝在 Promise 的 Catch 裡了,所以我們故意不處理 Catch,萬一真的發生錯誤,Mocha 會因為 Callback Function 沒有被呼叫而噴出 Timeout 錯誤,使該測試失敗。

至於如何驗證 Response 的資料一切正常,我們用到的是 這套 Assertion Library:

import chai from 'chai';
import { apiEngine } from '../../utils';
import todoAPI from '../../../build/common/api/todo';
import async from 'async';
import Todo from '../../../build/server/models/Todo';
let expect = chai.expect;
describe('#todoAPI', () => {
let fakeTodos = [{
text: 'this is a fake todo text',
}, {
text: 'foo',
}, {
text: '~bar~',
}];
before((done) => {
Todo.remove({}, done);
});
describe('#create()', () => {
it('should create todo', (done) => {
async.eachSeries(fakeTodos, (fakeTodo, cb) => {
todoAPI(apiEngine)
.create(fakeTodo)
.then((json) => {
expect(json.todo).to.be.an('object');
expect(json.todo.text).to.equal(fakeTodo.text);
cb();
});
}, done);
});
});
describe('#list()', () => {
it('should list todos', (done) => {
todoAPI(apiEngine)
.list({ page: 1 })
.then((json) => {
expect(json.todos).to.be.an('array');
expect(json.todos).to.have.lengthOf(fakeTodos.length);
done();
});
});
});
after((done) => {
Todo.remove({}, done);
});
});

完整程式碼:specs/endToEnd/apis/todo.jsopen_in_new

Chai 有很多語意化的 Chained Method 可以使用,讓我們可以很容易看懂測試的項目,例如上方程式中的 ,可以很直覺地知道這項測試的目的是:預期 todoAPI.create() 的回傳值中的 todo.text 要等於我們 Post 過去的 fakeTodo.text,其他的測試亦同理,Chai 還有很多奇奇怪怪的語法可以使用,建議讀者們遇到相對應的 Testing Scenario 再去查詢即可。