前端測試 Jest (未完待續)
1 配置環境
- node npm install --save-dev jest 需在環境變數中配置
- yarn yarn add --dev jest yarn global add jest
2 demo
jest 的工程檔案字尾都是xx.test.js,我們在執行一個jest工程的時候,jest會自動查詢執行的字尾為*.test.js的指令碼進行單元測試。
machers 眾多的匹配判斷器
- toBe() 用於檢驗基本資料型別的值是否相等
- toEqual() 用於檢驗引用資料型別的值,由於js本身object資料型別的本身特性,引用資料型別對比只是指標的對比,但是需要對比物件的每個值,所以這時候用到的是toEqual()
- Truthiness 布林值判斷的匹配器
- toBeNull 只匹配 null
- toBeUndefined 只匹配 undefined
- toBeDefined 與 toBeUndefined 相反
- toBeTruthy 匹配任何 if 語句為真
- toBeFalsy 匹配任何 if 語句為假
- 數字匹配器 用於判斷數字值之間的對比
- toBeGreaterThan 大於匹配器
- toBeGreaterThanOrEqual 大於等於匹配器
- toBeLessThan 小於匹配器
- toBeLessThanOrEqual 小於等於匹配器
- tobe 和 toequal 都是等價功能相同的對於數字
- toMatch 字串匹配器 和字串的match相同
- toContain 陣列匹配器 用於判斷陣列中是否包含某些值
- toThrow 報錯匹配器 用於測試特定的丟擲錯誤,可以判斷報錯語句的文字(支援正則匹配),也可以判斷報錯型別。
非同步程式碼的判斷
js 基於單執行緒非同步的特性,在程式碼中充斥的非同步程式和各種回撥,所以我們在測試非同步程式碼的時候要格外注意。一下是各種非同步的案例
- callback 回撥
test('the data is peanut butter', () => {
function callback(data) {
expect(data).toBe('peanut butter');
}
fetchData(callback);
});
// 這種情況有問題,大家都知道一個ajax請求是一個事件(由兩個事務傳送和返回組成),當傳送成功和返回成功這個事件就是完成的,就是成功的,這個時候jest就認為ajax事件完成了,所以檢驗會在回撥判斷執行前結束,沒有驗證data資料,所以這是有問題的。
複製程式碼
Jest 提供了相應的方法來done來解決這樣的問題,確保回撥執行的來完成整個test驗證。 使用單個引數呼叫 done,而不是將測試放在一個空引數的函式。Jest會等done回撥函式執行結束後,結束測試。 let's show code
test('the data is peanut butter', done => {
function callback(data) {
expect(data).toBe('peanut butter');
done();
}
fetchData(callback);
});
如果 done()永遠不會呼叫,這個測試將失敗,這也是你所希望發生的。
複製程式碼
- Promise 程式碼使用Promise處理非同步程式的話,我們有更簡單的方法進行非同步測試。如果Promise返回的狀態是reject則測試自動失敗。
test('Promise test', ()=>{
// 斷言測試
expect.assertions(1);
// 一定記得return,否則測試拿不到promise的狀態,測試會在fetchData()完成前完成。
return fetchData().then(data=>{
expect(data).tobe('promise')
})
})
複製程式碼
如果期望的promise是reject,我們可以通過catch來捕獲我們的錯誤。 請確保新增 expect.assertions 來驗證一定數量的斷言被呼叫。 否則一個fulfilled態的 Promise 不會讓測試失敗。
test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(e => expect(e).toMatch('error'));
});
複製程式碼
- .resolves / .rejects 匹配器 Jest有特殊的處理promise的匹配器,resolves和rejects。使用resolves匹配器處理promise,如果返回的狀態是reject則測試將自動失敗。切記不要丟失return。同理,你用rejects匹配器處理,如果你想得到promise返回是reject但是promise成功了返回一個成功的狀態resolve(fulfilled),則測試結果是失敗的。
// let's show code~
// resolves
test('the data is peanut butter', () => {
expect.assertions(1);
return expect(fetchData()).resolves.toBe('peanut butter');
});
// rejects
test('the fetch fails with an error', () => {
expect.assertions(1);
return expect(fetchData()).rejects.toMatch('error');
});
複製程式碼
- Async/Await 你也可以使用async/await 處理非同步測試。只需要在test的回撥函式前面新增async關鍵字。
// 提前瞭解async和await的使用方法和場景
test('the data is peanut butter', async () => {
// 切記新增斷言測試
expect.assertions(1);
const data = await fetchData();
expect(data).toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
// 切記新增斷言測試
try {
await fetchData();
} catch (e) {
expect(e).toMatch('error');
}
});
複製程式碼
還可以混合使用.resolves / .rejects和Async/Await
// 因為await本身返回的就是一個promise。所以我們可以在返回的promise的基礎之上繼續測試我們的程式碼。
test('the data is peanut butter', async () => {
expect.assertions(1);
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
await expect(fetchData()).rejects.toMatch('error');
});
複製程式碼
#Jest的遍歷 和 Setup 和 Teardown 進行一些重複的test設定的時候,我們可以使用beforeEach和afterEach。假設我們在一個互動資料上需要重複的做一些測試設定。假如測試前呼叫initializeCityDatabase()和測試後呼叫clearCityDatabase()。那我們就可以使用beforeEach和afterEach了。 let's show code~
beforeEach and afterEach
beforeEach(() => {
initializeCityDatabase();
});
afterEach(() => {
clearCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});
複製程式碼
beforeEach 和 afterEach處理非同步的程式碼,同樣可以採用done和promise,如果initializeCityDatabase()返回的是一個promise我們可以這樣處理他。let's show code
beforeEach(()=>{
return initializeCityDatabase(); // return一個promise
})
複製程式碼
beforeAll and afterAll
Scoping
我們可以通過Jest的describe函式,通過js函數語言程式設計的思想來建立一個作用域,來模組化我們的測試。具體場景就是的通過describe來模組我們的foreach的作用範圍。 let's show code
// 這裡的beforeEach適用於所有的test
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', () => {
// 這裡beforeEach僅僅適用於describe中的測試
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);
});
});
複製程式碼
既然這麼多beforeEach/afterEach和beforeAll/afterAll,那結合descibe來看看我們的執行順序。相信大家看完以後會想起js的(任務佇列和作用域)。
beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('', () => console.log('1 - test'));
describe('Scoped / Nested block', () => {
beforeAll(() => console.log('2 - beforeAll'));
afterAll(() => console.log('2 - afterAll'));
beforeEach(() => console.log('2 - beforeEach'));
afterEach(() => console.log('2 - afterEach'));
test('', () => console.log('2 - test'));
});
// 1 - beforeAll
// 1 - beforeEach
// 1 - test
// 1 - afterEach
// 2 - beforeAll
// 1 - beforeEach
// 2 - beforeEach
// 2 - test
// 2 - afterEach
// 1 - afterEach
// 2 - afterAll
// 1 - afterAll
正如結果檢視順序,全域性的會作用到describe內部。 注意執行順序,beforeALL和afterAll只會執行一次, 全域性的beforeEach作用到了describe中的test,還要注意的是after作用域中的after比全域性的after先執行。
beforeAll 在beforeEach前先執行,而afterAll在afterEach後執行。
複製程式碼
如果你想在眾多的test中,僅僅想進行其中的一個test,很簡單,你只要在他的test語句中新增.only標識就可以了,其他的測試則會跳過了.let's show code
// 只有這個test會跑
test.only('this will be the only test that runs', () => {
expect(true).toBe(false);
});
// 這個test則會跳過
test('this test will not run', () => {
expect('A').toBe('A');
});
複製程式碼