iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 22
0
Modern Web

30 天打造 MERN Stack Boilerplate系列 第 22

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);
  });
});

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

Assertion Part

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

至於如何驗證 Response 的資料一切正常,我們用到的是 Chai 這套 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.js

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


上一篇
Day 21 - Testing - Technique Stack
下一篇
Day 23 - Testing - Travis
系列文
30 天打造 MERN Stack Boilerplate30

尚未有邦友留言

立即登入留言