本文所有資料請參考:github.com/antgod/reac…
測試框架Jest
1. 快速開始
安裝jest
npm install --save-dev jest複製程式碼
我們先寫一個測試函式,有兩個數字引數做加法,首先,建立sum.js
檔案
function sum(a, b) {
return a + b
}
module.exports = sum複製程式碼
然後,建立建立sum.test.js
,包含我們目前的測試程式碼。
const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});複製程式碼
然後新增片段到package.json中
{
"scripts": {
"test": "jest"
}
}複製程式碼
最後執行npm test
,jest輸入如下內容
PASS ./sum.test.js
✓ adds 1 + 2 to equal 3 (5ms)複製程式碼
你剛剛成功的編寫了一個jest測試!
這個測試用例使用了expect
和toBe
進行兩個值相同的測試。想要學習更多的關於jest測試,請參考 Using Matchers
從命令列執行
你也可以直接從命令列執行,可以輸入一些有空的引數(npm install jest -g)。
Here's how to run Jest on files matching my-test, using config.json as a configuration file and display a native OS notification after the run:
jest my-test --notify --config=config.json複製程式碼
如果你想學習更多的命令列執行,請參考 Jest CLI Options
附加配置
使用 Babel:
安裝babel-jest
和 regenerator-runtime
包:
npm install --save-dev babel-jest regenerator-runtime複製程式碼
注意: 如果你使用npm 3或者npm 4,你不用指明安裝regenerator-runtime
。
新增一份.babelrc
檔案到你的工程根目錄,比如,如果你使用es6或者react.js需要使用babel-preset-es2015
和babel-preset-react
預設:
{
"presets": ["es2015", "react"]
}複製程式碼
這樣你會使用es6與react所有指定的語法。
注意: 如果你使用更多的babel編譯配置,請使用babel's env option
,記住jest將會自動定義node_env作為測試。
使用webpack
jest可以實用在工程內使用webpack管理你的資產,樣式和編輯。webpack 提供一些特別的功能相比於其他工具。更多資料請參考 webpack guide
2. 使用匹配
普通匹配
jest使用matchers
讓你通過不同的方法測試值。你需要熟記很多不同的matchers
。這裡只介紹最常用的matchers
。
最簡單的測試值相等的是精確相等:
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});複製程式碼
上段程式碼,expect(2 + 2)
返回一個“期待”物件。除了呼叫matchers
,關於期待物件你不需要做太多。在這段程式碼中,.toBe(4)
是一個matcher。當jest執行時,將追蹤所有失敗的matchers
,所以它可以精確的列印錯誤資訊。
toBe
使用===
精確等於測試。如果你想深度測試物件相等,使用toEqual
代替。
test('object assignment', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
});複製程式碼
toEqual
遞迴的查詢每個欄位對比是否相等。
你可以使用not
去測試matcher
的反面:
test('adding positive numbers is not zero', () => {
for (let a = 1; a < 10; a++) {
for (let b = 1; b < 10; b++) {
expect(a + b).not.toBe(0);
}
}
});複製程式碼
型別判斷
有時候你需要判斷undefined,null與false,但有時候你不需要明確的區分他們。jest包含工具明確的區分他們。
- toBeNull matches only null
- toBeUndefined matches only undefined
- toBeDefined is the opposite of toBeUndefined
- toBeTruthy matches anything that an if statement treats as true
- toBeFalsy matches anything that an if statement treats as false
舉個例子:
test('null', () => {
const n = null;
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).not.toBeUndefined();
expect(n).not.toBeTruthy();
expect(n).toBeFalsy();
});
test('zero', () => {
const z = 0;
expect(z).not.toBeNull();
expect(z).toBeDefined();
expect(z).not.toBeUndefined();
expect(z).not.toBeTruthy();
expect(z).toBeFalsy();
});複製程式碼
你可以使用這些matcher
做精確的匹配。
數字
多種途徑比較數字
test('two plus two', () => {
const value = 2 + 2;
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3.5);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4.5);
// toBe and toEqual are equivalent for numbers
expect(value).toBe(4);
expect(value).toEqual(4);
});複製程式碼
你可以使用toBeCloseTo
進行浮點比較
test('adding floating point numbers', () => {
const value = 0.1 + 0.2;
expect(value).not.toBe(0.3); // 浮點數不會直接相等
expect(value).toBeCloseTo(0.3); // 使用closeTo方法進行浮點數字比較
});複製程式碼
字串
你可以使用toMatch
測試正規表示式來驗證string字串
test('there is no I in team', () => {
expect('team').not.toMatch(/I/);
});
test('but there is a "stop" in Christoph', () => {
expect('Christoph').toMatch(/stop/);
});複製程式碼
陣列
你可以檢驗陣列是否包含某一個特別項
const shoppingList = [
'diapers',
'kleenex',
'trash bags',
'paper towels',
'beer',
];
test('the shopping list has beer on it', () => {
expect(shoppingList).toContain('beer');
});複製程式碼
表示式
你可以使用toThrow
來檢驗函式是否丟擲異常
function compileAndroidCode() {
throw new ConfigError('you are using the wrong JDK');
}
test('compiling android goes as expected', () => {
expect(compileAndroidCode).toThrow();
expect(compileAndroidCode).toThrow(ConfigError);
// You can also use the exact error message or a regexp
expect(compileAndroidCode).toThrow('you are using the wrong JDK');
expect(compileAndroidCode).toThrow(/JDK/);
});複製程式碼
更多
這是一個just嘗試,檢視完整的matchers
列表renerence docs。
一旦你掌握了一個可用的matcher
,建議下一步學習jest如何檢驗非同步程式碼
3.測試非同步程式碼
js執行非同步程式碼是很普遍的。jest
需要知道什麼時候程式碼測試已完成,才能移動到下一個測試。jest
有幾種方法處理。
Callbacks
最普遍的非同步是通過回撥函式。
比如,如果你呼叫fetchData(callback)
函式去拉取非同步資料並且結束時候呼叫callback(data)
。你要測試的資料是否等於peanut buter
。
預設情況下,Jest
走完測試程式碼就完成測試。這意味著測試不能按期進行。
// Don't do this!
test('the data is peanut butter', () => {
function callback(data) {
expect(data).toBe('peanut butter');
}
fetchData(callback);
});複製程式碼
問題在於測試工作將在fetchData
結束的時候,也就是在回撥函式之前結束。
為了解決這個問題,這是另一種形式。使用引數done
來代替無參函式。Jest
將會延遲測試直到done
回撥函式執行完畢。
test('the data is peanut butter', done => {
function callback(data) {
expect(data).toBe('peanut butter');
done();
}
fetchData(callback);
});複製程式碼
如果done
一直沒有呼叫,測試將會失敗。
Promises
如果你使用promise,有一種簡單的方法處理非同步測試。你的測試程式碼中Jest
返回一個Promise
,並且等待Promise
去resolve
。如果Promise
是rejected
,測試自動失敗。
比如,還是那個fetchData
,這次使用了回撥,返回一個promise
假定reslove
一個字串peanut butter
。測試程式碼如下:
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});複製程式碼
確保返回一個Promise-如果你省略了return
,你的測試程式碼將會在fetchData
之前結束。
你也可以使用resolves
關鍵字在你的expect
程式碼後,然後Jest
將會把等待狀態轉換成resolve
。如果promise
被rejected
,測試自動失敗。
test('the data is peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});複製程式碼
Async/Await
如果你使用async/await
,你可以完美嵌入測試。寫一個async
測試,僅使用async
關鍵字在你的matchers
函式前面就能通過測試。比如,還是fetchData
這個測試方案可以寫成
test('the data is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});複製程式碼
在這種情況下,async/await
僅僅是個有效的promises
樣例的邏輯語法糖。
這些表單特別優秀,你可以混合使用這些在你的程式碼庫或者單個檔案中,它僅僅使你的測試變得更加簡單。
安裝與解除安裝
寫測試的過程中,在你執行測試之前,你需要做一些初始化工作,去做一些需要測試的事情之前,並且你需要做一些結束工作,去做一些測試結束的事情。Jest
提供了helper
函式去處理這些工作。
多測試重複初始化
如果你有很多測試有重複的工作,你可以是使用beforeEach
與afterEach
。
比如你有很多測試執行之前需要呼叫initializeCityDatabase()
,而且測試結束 後需要呼叫clearCityDatabase()
。你可以這麼做:
beforeEach(() => {
initializeCityDatabase();
});
afterEach(() => {
clearCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});複製程式碼
同樣的,如果你的initializeCityDatabase
函式返回一個promise
,你可以使用return
返回這個函式。
beforeEach(() => {
return initializeCityDatabase();
});複製程式碼
一次性設定
如果你有很多測試有共同的重複的工作,並且重複的工作只在測試開始與測試結束的地方執行一次,你可以是使用beforeAll
與beforeAll
。
比如你有一個測試開始之前要呼叫initializeCityDatabase()
,而且測試結束 後需要呼叫clearCityDatabase()
。你可以這麼做:
beforeAll(() => {
return initializeCityDatabase();
});
afterAll(() => {
return clearCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});複製程式碼
範圍
預設情況下,before
與after
會對檔案的每個測試生效。如果你只想對某些測試生效,你可以使用describe
塊。 before
與after
僅僅會在宣告塊中執行。
比如,我們不僅需要城市初始化,還需要食物初始化,我們可以對不同的測試做不同的初始化。
// Applies to all tests in this file
beforeEach(() => {
return initializeCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});
describe('matching cities to foods', () => {
// Applies only to tests in this describe block
beforeEach(() => {
return initializeFoodDatabase();
});
test('Vienna <3 sausage',="" ()=""> {
expect(isValidCityFoodPair('Vienna', 'Wiener Schnitzel')).toBe(true);
});
test('San Juan <3 plantains',="" ()=""> {
expect(isValidCityFoodPair('San Juan', 'Mofongo')).toBe(true);
});
});3>3>複製程式碼
建議
如果你僅僅想測試一個測試用例,或者跨過某個測試用例,你可以使用fit
與xit
。
fit('this will be the only test that runs', () => {
expect(true).toBe(false);
});
xit('this will be the only test that runs', () => {
expect(true).toBe(false);
});
test('this test will not run', () => {
expect('A').toBe('A');
});複製程式碼