上一篇介紹了如何自行寫出能執行的測試,不過實際上大部分時候我們都不該這樣做,我們應該使用開源軟體,輕鬆地站在巨人的肩膀上。
後面的 JavaScript 相關測試與我個人的 Best Practices 都會搭配著 Jest 介紹,不過熟知各種不同的 runner 還是有其必要性,畢竟有時候專案用什麼你就得跟著去使用。JavaScript 比較常見的 runner 大概有一下幾種,稍微知道即可:
Jest — 我最推薦的 runner,可以從小專案用到大專案
Mocha — 作為非常老牌的 runner,還是有一定的出現機率
Tap — 非常輕量簡單的 runner,實作跨語言的 Test Anything Protocol
接下來要來教大家如何用 Jest 換掉原本自行執行的測試。
安裝 Jest
首先是要先把 Jest 安裝到 devDependencies
裡,看你是用 npm
還是 yarn
:
npm install -D jest// 如果你是用 yarn 安裝的話
yarn add -D jest
接著要來把在 package.json
裡面的 test
npm script 換掉,換成 jest
指令:
// package.json
{
"scripts": {
"test": "jest"
},
"devDependencies": {
"jest": "^27.3.1"
}
}
這樣就算是裝好 Jest 的測試環境了。
Jest 怎樣認定測試檔案
在目前這個版本,Jest 搜尋測試檔案是使用下面這兩個 glob pattern:
- **/__tests__/**/.[jt]s?x
- **?(*.)+(spec|test).[jt]s?(x)
也就是說在不修改設定的情況下,你必須把測試檔案放入 __tests__
資料夾,或是命名為 spec.js
或是 test.js
這類的檔名。
我們在上一篇使用了 test.js
這個檔名,所以在這邊剛好是無縫接軌的,可以直接進入改寫測試檔的步驟。
用 Jest 改寫 assert 的測試
我們上一篇所寫的一些測試,把它們全部整理起來的話,大概會是這個樣子:
const assert = require('assert');const items = [1, 2, 3].map(number => `item_${number}`);assert(items[0] === 'item_1', '第 1 個 item 應該是 item1');
assert(items[1] === 'item_2', '第 2 個 item 應該是 item2');
assert(items[2] === 'item_3', '第 3 個 item 應該是 item3');assert(' A'.trim() === 'A', '左邊能 trim 掉');
assert('A '.trim() === 'A', '右邊能 trim 掉');
assert(' A '.trim() === 'A', '兩邊都能 trim 掉');
assert(' A A '.trim() === 'A A', '中間不會被 trim 掉');function doSomething(callback) { /** **/ }let fnBeCalledWithArgs;
const fn = (...args) => {
fnBeCalledWithArgs = args
};doSomething(fn);assert(fnBeCalledWithArgs, 'callback 有被 call');
assert(fnBeCalledWithArgs[0] === 10000, '被 call 時參數是 10000');
其中大致包含了三項不同的測試,我們這邊就一個一個來改寫它們。
用 Jest 改寫 Array.prototype.map(...)
的測試
首先是關於 Array.prototype.map(...)
的測試:
const assert = require('assert');const items = [1, 2, 3].map(number => `item_${number}`);assert(items[0] === 'item_1', '第 1 個 item 應該是 item1');
assert(items[1] === 'item_2', '第 2 個 item 應該是 item2');
assert(items[2] === 'item_3', '第 3 個 item 應該是 item3');
我們需要把定義的測試用 it
包起來,並給它一個適當的描述。再來,我們不需要引入 assert
了,取而代之的是 Jest global 有一個更好用的斷言(Assertion)語法 — expect
可以用,toEqual
這邊可以幫我們檢查兩個陣列是否相等( deep equality):
it('Array.prototype.map(...) 應該回傳包含映射結果的新陣列', () => {
const items = [1, 2, 3].map(number => `item_${number}`); expect(items).toEqual(['item_1', 'item_2', 'item_3');
});
好的 assertion 可以讓測試可讀性變高,也可以避免在測試寫太多邏輯,可以得到更高的可靠性。可以在官網去看詳細的 expect API 。
用 Jest 改寫 String.prototype.trim()
的測試
接著來看 String.prototype.trim()
的測試:
const assert = require('assert');assert(' A'.trim() === 'A', '左邊能 trim 掉');
assert('A '.trim() === 'A', '右邊能 trim 掉');
assert(' A '.trim() === 'A', '兩邊都能 trim 掉');
assert(' A A '.trim() === 'A A', '中間不會被 trim 掉');
跟前面一樣,用 it
把描述寫清楚,裡面換成 Jest 的 expect
:
it('String.prototype.trim() 應該能把兩邊空白拿掉', () => {
expect(' A'.trim()).toBe('A');
expect('A '.trim()).toBe('A');
expect(' A '.trim()).toBe('A');
expect(' A A '.trim()).toBe('A A');
});
如果希望這個項目測試的更細緻一點的話,可以選擇把 case 分成更細一點:
it('String.prototype.trim() 應該能把左邊空白拿掉', () => {
expect(' A'.trim()).toBe('A');
});it('String.prototype.trim() 應該能把右邊空白拿掉', () => {
expect('A '.trim()).toBe('A');
});it('String.prototype.trim() 不會把中間空白 trim 掉', () => {
expect(' A A '.trim()).toBe('A A');
});
用 Jest 改寫 Side Effect 的測試
最後是改寫 Side Effect 的測試
const assert = require('assert');function doSomething(callback) { /** **/ }let fnBeCalledWithArgs;
const fn = (...args) => { fnBeCalledWithArgs = args };doSomething(fn);assert(fnBeCalledWithArgs, 'callback 有被 call');
assert(fnBeCalledWithArgs[0] === 10000, '被 call 時參數是 10000');
這邊我們可以用 jest.fn()
來製造假 function 傳進去,就可以直接用 toBeCalledWith
的 assertion,非常方便:
function doSomething(callback) { /** **/ }it('doSomething 會用 10000 當參數呼叫 callback', () => {
const fn = jest.fn(); doSomething(fn); expect(fn).toBeCalledWith(10000);
});
這樣一來三個部分就改好啦。
結語
Jest 是彈性很高但實際上上手很簡單的 runner,希望這篇有讓初學的人都能體會到這一點。下一篇要來講講怎麼做 server side 的測試。