前言
近幾年,前端發展速度很快,這也意味著對前端工程化的要求會越來越高,而前端自動化測試作為程式碼質量保證的一環,也是前端工程化的範疇,目前很多開源框架或庫都使用了前端自動化測試,例如Ant Design
、Element UI
等,所以我們有必要學習一下前端自動化測試。
Jest
是facebook推出的一款測試框架,整合了Mocha,chai,jsdom,sinon等功能。所以其強大的功能還是值得我們學習的。
jest的簡單配置
jest
是執行在node
環境下的,不支援es6
的Es Module
,但我們測試程式碼通常是執行在瀏覽器的,所以有必要使用Babel
進行程式碼轉換,下面對專案進行初始化和jest的簡單配置:
執行
npm init
複製程式碼
進行專案初始化,並在package.json
中配置測試的script
命令:
"script": {
"test": 'jset'
}
複製程式碼
再安裝jest、@babel/core、@babel/preset-env
npm i jest @babel/core @babel/preset-env -D
複製程式碼
安裝完成後,執行
npx jest --init
複製程式碼
進行jest配置初始化,初始化完成後,就是再目錄多出一個jest.config.js
配置檔案了。
接著建立.babelrc
進行babel配置:
// .babelrc
{
"presets": [
["@babel/preset-env", {
"target": {
"node": "current"
}
}]
]
}
複製程式碼
經過這些配置後,我們就可以在jest
的測試檔案中使用Es Module
了。使用jest
進行測試時,我們對模組的測試需要遵循一定的檔案命名,命名規則為:
moduleName.test.js
複製程式碼
例如需要對button.js
檔案模組進行測試,我們為此建立的測試檔名為button.test.js
。
一、前端自動化測試的原理
Jest
擁有眾多API
,可以測試各種開發場景,其核心API
是expect()
和test()
,每個測試用例都離不開這兩個核心功能。
test()
函式主要用於描述一個測試用例,expect()
函式是用於指示我們期望的結果。而expect()
和test()
原理思路很巧妙,並不複雜,下面程式碼就是兩者的原理思路:
function expect (result) {
return {
toBe: function (actual) {
if (result !== actual) {
throw new Error(`
預期值與實際值不相等,預期值為${actual},實際值為${result}`);
}
}
}
}
function test (desc, callback) {
try {
callback();
console.info(`${desc} 通過測試`);
} catch(e) {
console.info(`${desc} 沒有通過測試:${e}`);
}
}
複製程式碼
在這段程式碼中expact(result)
將返回我們期望的結果,通常情況下我們只需要呼叫expact(result)
就可以,括號中的可以是一個具有返回值的函式,也可以是表示式。後面的toBe
就是一個matcher,當Jest執行的時候它會記錄所有失敗的matcher的詳細資訊並且輸出給使用者,讓維護者清楚的知道failed的原因。
二、Jest的API
2.1、 Matchers匹配器
匹配器(Matchers)是Jest中非常重要的一個概念,它可以提供很多種方式來讓你去驗證你所測試的返回值,匹配器可以通俗理解為相等操作。
2.1.1、相等匹配Matchers
- toBe()
toBe()
是測試expect
的期望值是否全等於結果值。toBe()
相當於js中的===、Object.is()
。
test('測試具體值:1 + 2 = 3',() => {
expect(1 + 2).toBe(3);
});
複製程式碼
- toEqual()
相對於toBe()
的完全相等,toEqual()
匹配的是內容是否相等,一般用於測試物件或陣列的內容相等。
test('測試物件內容相等',() => {
let a = { a: 1 };
expect(a).toEqual({ a: 1 });
});
複製程式碼
2.1.2、判斷匹配Matchers
toBeTruthy()
是測試expect
的期望值是否為true
;
toBeFalsy()
是測試expect
的期望值是否為false
;
toBeUndefined()
是測試expect
的期望值是否為undefined
;
toBeNull()
是測試expect
的期望值是否為null
;
test('判斷匹配Matchers',() => {
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect().toBeUndefined();
expect(null).toBeNull();
});
複製程式碼
2.1.3、數字匹配Matchers
toBeGreaterThan()
是測試expect
的期望值是否大於
傳入的引數;
toBeLessThan()
是測試expect
的期望值是否小於
傳入的引數;
toBeGreaterThanOrEqual()
是測試expect
的期望值是否大於或等於
傳入的引數;
toBeLessThanOrEqual()
是測試expect
的期望值是否否小於或等於
傳入的引數;
test('判斷匹配Matchers',() => {
expect(2).toBeGreaterThan(1);
expect(2).toBeLessThan(3);
expect(2).toBeGreaterThanOrEqual(2);
expect(2).toBeLessThanOrEqual(2);
});
複製程式碼
2.1.4、其他常用匹配Matchers
toMatch()
是測試expect
的期望值是否符合傳入的正規表示式
匹配規則;
toContain()
是測試expect
的期望值是否包含
傳入的引數;
toThrow()
是測試expect
的期望值是否會丟擲特定的異常資訊;
test('其他常用匹配Matchers',() => {
expect(/2/).toMatch(2);
expect('Other Matchers').toContain('Matchers');
expect(() => { throw new Error('error'); }).toThrow('error');
});
複製程式碼
jest
的匹配器有很多,但是我們不必全部都記住,可以靈活運用各種匹配器達到類似的效果,比如toBe(true)
類似於toBeTruthy
。
三、Jest測試非同步程式碼
我們在開發過程中,難免會進行資料請求等非同步操作,Jest
也考慮到了這一點,現在我們以非同步請求資料為例,來說明如何使用Jest
進行非同步程式碼測試:
執行
npm i axios --save
複製程式碼
3.1、回撥函式非同步型別測試
建立fetch.js
:
//fetch.js
import axios from 'axios';
//假設'https://juejin.im/editor'返回的data資料為{ success: true }
export const fetchData = (callback) => {
aixos.get('https://juejin.im/editor').then(res => {
callback(res.data);
});
}
複製程式碼
接著建立fetch.test.js
進行測試:
//fetch.test.js
import { fetchData } from './fetchData.js';
test('測試返回的結果為 { success: true }', (done) => {
fetchData((data) => {
expect(data).toEqual({ success: true });
done();
})
});
複製程式碼
3.2、返回Promise非同步型別測試
3.2.1、Promise請求成功的測試
建立fetch.js
:
//fetch.js
import axios from 'axios';
//假設'https://juejin.im/editor'返回的data資料為{ success: true }
export const fetchData = (callback) => {
return aixos.get('https://juejin.im/editor');
}
複製程式碼
接著建立fetch.test.js
進行測試:
//fetch.test.js
import { fetchData } from './fetchData.js';
test('測試返回的結果為 { success: true }', () => {
return fetchData().then(res => {
const { data } = res;
expect(data).toEqual({ success: true });
})
});
複製程式碼
3.2.2、Promise請求失敗的測試
建立fetch.js
:
//fetch.js
import axios from 'axios';
//假設'https://juejin.im/editor/xxxx'沒有返回資料,為404狀態
export const fetchData = (callback) => {
return aixos.get('https://juejin.im/editor/xxxx');
}
複製程式碼
接著建立fetch.test.js
進行測試:
//fetch.test.js
import { fetchData } from './fetchData.js';
test('測試返回的結果為 404', () => {
expect.assertions(1);
return fetchData().catch(e => {
expect(e.toString().indexOf('404') > -1).toBe(true);
})
});
複製程式碼
四、Jest的鉤子函式
Jest的鉤子函式類似於Vue
的生命週期函式,會在在程式碼執行的特定時刻,自動執行一個函式。Jest中有4個核心的鉤子函式,分別為beforeAll、beforeEach、afterEach、afterAll
,鉤子函式均接受回撥函式作為引數。
4.1、Jest的鉤子函式執行順序
顧名思義,Jest的4個核心鉤子函式執行順序依次為beforeAll
、beforeEach
、afterEach
、afterAll
。
beforeAll:
該鉤子函式會在所有
測試用例執行之前
執行,通常用於進行初始化。beforeEach:
該鉤子函式會在每個
測試用例執行之前
執行。afterEach:
該鉤子函式會在每個
測試用例執行之後
執行。afterAll:
該鉤子函式會在所有
測試用例執行之後
執行。
下面以hook.test.js
為例來說明一下鉤子函式的執行順序:
//hook.test.js
beforeAll(() => {
console.info('beforeAll 鉤子函式執行');
})
beforeEach(() => {
console.info('beforeEach 鉤子函式執行');
})
afterEach(() => {
console.info('afterEach 鉤子函式執行');
})
afterAll(() => {
console.info('afterAll 鉤子函式執行');
})
test('測試Jest中的鉤子函式', () => {
console.info('測試用例執行');
expect(1).toBe(1);
});
複製程式碼
執行
npm run test
複製程式碼
我們會發現終端,列印出以下資訊:
'beforeAll 鉤子函式執行'
'beforeEach 鉤子函式執行'
'測試用例執行'
'afterEach 鉤子函式執行'
'afterAll 鉤子函式執行'
複製程式碼
這也恰恰印證了上面說明的鉤子函式的執行順序。
4.2、藉助鉤子函式,提高程式碼維護性
合理運用鉤子函式,可以使我們的測試程式碼更加易於維護,下面以Calculator.js
為例來說明一下鉤子函式的運用:
建立Calculator.js
:
//Calculator.js
class Calculator {
constructor() {
this.number = 0
}
add() {
this.number += 1;
}
minus() {
this.number -= 1;
}
}
複製程式碼
接著建立Calculator.test.js
進行測試:
//Calculator.test.js
import Calculator from './Calculator.js';
let calculator = null;
//Jest推薦使用鉤子函式進行初始化
beforeAll(() => {
calculator = new Calculator();
})
test('測試Calculator中的 add 方法', () => {
calculator.add();
expect(calculator.number).toBe(1);
});
test('測試Calculator中的 minus 方法', () => {
//共用了同一個```calculator```例項,與上一個程式碼耦合
calculator.minus();
expect(calculator.number).toBe(0);
});
複製程式碼
執行
npm run test
複製程式碼
我們會發現測試用例是順利通過的,不過Calculator.test.js
中的程式碼寫法有一個問題:在每個測試用例中都共用了同一個calculator
例項,導致每個測試函式互相耦合,不利於維護,為了解決這個問題我們可以藉助Jest
的beforeEach
鉤子函式進行解耦。
修改Calculator.test.js
:
//Calculator.test.js
import Calculator from './Calculator.js';
let calculator = null;
beforeEach(() => {
calculator = new Calculator();
})
test('測試Calculator中的 add 方法', () => {
calculator.add();
expect(calculator.number).toBe(1);
});
test('測試Calculator中的 minus 方法', () => {
calculator.minus();
expect(calculator.number).toBe(-1);
});
複製程式碼
執行
npm run test
複製程式碼
我們會發現測試用例是順利通過的,通過beforeEach
鉤子函式,我們在每個測試用例執行之前,重新建立了calculator
例項,這樣每個測試用例之間就沒有互相耦合,程式碼會更加容易維護。
五、藉助describe進行測試用例分組管理
我們在開發過程中都會遇到功能繁多的複雜模組,為此我們需要寫大量測試用例來測試這些模組,但如果單純為模組的每個功能寫測試用例而不加以分類
的話,Jest
測試檔案將十分混亂而難以維護,因此我們需要對模組功能進行分類,在藉助Jest
的describe進行分組管理。
以上面的Calculator.js
以及Calculator.test.js
為例,來說明如何使用describe進行分組管理:
修改Calculator.js
:
//Calculator.js
class Calculator {
constructor() {
this.number = 0
}
add() {
this.number += 1;
}
minus() {
this.number -= 1;
}
multiply() {
this.number *= 2;
}
divide() {
this.number /= 10;
}
}
複製程式碼
修改Calculator.test.js
:
//Calculator.test.js
import Calculator from './Calculator.js';
describe('測試Calculator模組所有功能',() => {
let calculator = null;
beforeEach(() => {
calculator = new Calculator();
});
describe('測試Calculator中增加數量相關的方法',() => {
test('測試Calculator中的 add 方法', () => {
calculator.add();
expect(calculator.number).toBe(1);
});
test('測試Calculator中的 multiply 方法', () => {
calculator.multiply();
expect(calculator.number).toBe(0);
});
});
describe('測試Calculator中減少數量相關的方法',() => {
test('測試Calculator中的 minus 方法', () => {
calculator.minus();
expect(calculator.number).toBe(-1);
});
test('測試Calculator中的 divide 方法', () => {
calculator.divide();
expect(calculator.number).toBe(0);
});
});
})
複製程式碼
執行
npm run test
複製程式碼
我們會發現終端,列印出以下層次分明、可讀性良好
的測試資訊:
'測試Calculator模組所有功能'
'測試Calculator中增加數量相關的方法'
✔ '測試Calculator中的 add 方法'
✔ '測試Calculator中的 multiply 方法'
'測試Calculator中減少數量相關的方法'
✔ '測試Calculator中的 minus 方法'
✔ '測試Calculator中的 divide 方法'
複製程式碼
我們可以看到,對模組功能進行分類,以及藉助Jest
的describe進行分組管理,我們測試程式碼的可維護性以及測試執行結果資訊可讀性都有顯著的提高了。
六、describe中的鉤子函式執行規則
每個describe
的回撥函式都有各自的作用域,都可以使用Jest
的4個核心鉤子函式,而describe
中的鉤子函式都可以作用於它回撥函式中的所有測試用例。
面對像上面Calculator.test.js
檔案中,describe
回撥函式巢狀describe
的場景,每個describe
中的鉤子函式執行順序會有點特別。
以下對Calculator.test.js
進行修改,來說明describe
回撥函式巢狀describe
場景的鉤子函式執行順序:
修改Calculator.test.js
:
//Calculator.test.js
import Calculator from './Calculator.js';
describe('測試Calculator模組所有功能',() => {
let calculator = null;
beforeAll(() => {
console.info('beforeAll: 父級 beforeAll 執行');
});
beforeEach(() => {
calculator = new Calculator();
console.info('beforeEach: 父級 beforeEach 執行');
});
afterEach(() => {
console.info('afterEach: 父級 afterEach 執行');
});
describe('測試Calculator中增加數量相關的方法',() => {
beforeAll(() => {
console.info('beforeAll: 第一個子級 beforeAll 執行');
});
test('測試Calculator中的 add 方法', () => {
console.info('測試Calculator中的 add 方法');
calculator.add();
expect(calculator.number).toBe(1);
});
});
describe('測試Calculator中減少數量相關的方法',() => {
beforeEach(() => {
console.info('beforeEach: 第二個子級 beforeEach 執行');
});
test('測試Calculator中的 minus 方法', () => {
console.info('測試Calculator中的 minus 方法');
calculator.minus();
expect(calculator.number).toBe(-1);
});
});
})
複製程式碼
執行
npm run test
複製程式碼
我們會發現終端,列印出以下資訊:
'beforeAll: 父級 beforeAll 執行'
'beforeAll: 第一個子級 beforeAll 執行'
'beforeEach: 父級 beforeEach 執行'
'測試Calculator中的 add 方法'
'afterEach: 父級 afterEach 執行'
'beforeEach: 父級 beforeEach 執行'
'beforeEach: 第二個子級 beforeEach 執行'
'測試Calculator中的 minus 方法'
'afterEach: 父級 afterEach 執行'
複製程式碼
從中,我們可以發現在describe
回撥函式巢狀describe
的場景下,describe
中的鉤子函式都可以作用於它回撥函式中的所有測試用例,並且每個測試用例在describe
作用域中執行時,相同型別
鉤子函式的執行順序是先從父級到子級
、從外部到內部
的。
七、Jest中的Mock
從測試的角度來說我只關心測試的方法的內部邏輯,並不關注與當前方法本身依賴的實現,所以,我們在測試某些依賴了外部的一些介面的實現的方法時,通常會進行Mock實現依賴介面的返回,只測試方法的內部邏輯而規避外部的依賴,基於這個思想,jest
中提供了強大的Mock
功能,方便開發者進行mock操作。
7.1、使用jest.fn()
函式,捕獲函式的呼叫
開發過程中,一個功能函式傳入回撥函式作為引數的場景很常見,如果想測試這類功能函式,我們就需要藉助Mock
函式,捕獲函式的呼叫。
以callback.js
以及callback.test.js
為例,來說明Mock
函式:
建立callback.js
:
//callback.js
export const testCallback = (callback) => {
callback();
}
複製程式碼
建立callback.test.js
:
//callback.test.js
import { testCallback } from './callback.js';
test('測試 testCallback 方法', () => {
const fn = jest.fn();
testCallback(fn);
expect(fn).toBeCalled();
})
複製程式碼
執行
npm run test
複製程式碼
我們會發現測試用例順利通過,上面程式碼通過jest.fn()
mock出一個fn
函式作為testCallback
的回撥函式,在使用toBeCalled
來捕獲fn
的呼叫情況來驗證測試結果。值得注意的是,只有jest.fn()
mock出的函式才可以被toBeCalled
捕獲。
7.2、Mock函式的.mock
屬性
又jest.fn()
mock出的函式會有一個.mock
屬性,藉助.mock
屬性我們可以多種方式去測試功能模組。
以上面的callback.js
以及callback.test.js
為例,來說明Mock
函式:
修改callback.test.js
:
//callback.test.js
import { testCallback } from './callback.js';
test('測試 testCallback 方法', () => {
const fn = jest.fn();
testCallback(fn);
testCallback(fn);
console.info(fn.mock);
expect(fn).toBeCalled();
})
複製程式碼
執行後,我們會發現終端,列印出以下.mock
屬性資訊:
{
calls: [ [], [] ],
instances: [ undefined, undefined ],
invocationCallOrder: [ 1, 2 ],
results: [
{ type: 'return', value: undefined },
{ type: 'return', value: undefined }
]
}
複製程式碼
7.2.1、mock物件的calls
屬性
fn.mock
中的calls
屬性是一個二維陣列,二維陣列中的陣列項表示傳入jest.fn()
mock出的函式的實參,類似於arguments
屬性。
從上面callback.test.js
可以看到,fn
是由jest.fn()
mock出的函式,fn
傳入testCallback
中被呼叫了兩次並且fn
並沒有接受引數,所以,calls
二維陣列length
為2,有2個空陣列項。
我們可以修改callback.js
,給callback
傳入引數,看一下此時calls
的變化:
//callback.js
export const testCallback = (callback) => {
callback(1, 2, 3);
}
複製程式碼
執行callback.test.js
後,我們會發現終端,列印出以下.mock
屬性資訊:
{
calls: [ [1, 2, 3], [1, 2, 3] ],
instances: [ undefined, undefined ],
invocationCallOrder: [ 1, 2 ],
results: [
{ type: 'return', value: undefined },
{ type: 'return', value: undefined }
]
}
複製程式碼
我們可以發現calls
變為了[ [1, 2, 3], [1, 2, 3] ]
,也就是說calls
的陣列項的確是表示傳入jest.fn()
mock出的函式的實參。
7.2.2、mock物件的invocationCallOrder
屬性
fn.mock
中的invocationCallOrder
屬性是一個陣列,陣列指示jest.fn()
mock出的函式執行的順序。
從上面callback.test.js
可以看到,fn
是由jest.fn()
mock出的函式,fn
傳入testCallback
中被呼叫了兩次,所以,invocationCallOrder
陣列的length
為2。
7.2.3、mock物件的results
屬性
fn.mock
中的results
屬性是一個陣列,陣列中的物件指示jest.fn()
mock出的函式每次執行的返回值,返回值由value
屬性表示。
從上面callback.test.js
可以看到,fn
是由jest.fn()
mock出的函式,fn
傳入testCallback
中被呼叫了兩次並且fn
並沒有返回值,所以,results
陣列的length
為2,有2個物件,物件中的value
屬性均為undefined
。
我們可以修改callback.test.js
,使fn
有返回值,看一下此時results
的變化:
//callback.test.js
import { testCallback } from './callback.js';
test('測試 testCallback 方法', () => {
const fn = jest.fn(() => {
return 123;
});
testCallback(fn);
testCallback(fn);
console.info(fn.mock);
expect(fn).toBeCalled();
})
複製程式碼
執行callback.test.js
後,我們會發現終端,列印出以下.mock
屬性資訊:
{
calls: [ [1, 2, 3], [1, 2, 3] ],
instances: [ undefined, undefined ],
invocationCallOrder: [ 1, 2 ],
results: [
{ type: 'return', value: 123 },
{ type: 'return', value: 123 }
]
}
複製程式碼
我們可以發現results
中的物件的value
變為了fn
的返回值123
,也就是說results
陣列中的物件的確指示jest.fn()
mock出的函式每次執行的返回值,返回值由value
屬性表示。
7.2.4、mock物件的instances
屬性
fn.mock
中的instances
屬性是一個陣列,陣列指示jest.fn()
mock函式的this
指向。當mock函式當作普通函式呼叫時,this
指向undefined
;當mock函式當作建構函式被new
例項化時,this
指向mockConstructor{}
。
從上面callback.test.js
可以看到,fn
是由jest.fn()
mock出的函式,fn
作為回撥函式傳入testCallback
中被呼叫了兩次,所以,fn
的this
指向undefined
,instances
陣列的length
為2,陣列項均為undefined
。
我們可以修改callback.js
,使fn
以建構函式形式被呼叫,看一下此時instances
的變化。
修改callback.js
:
//callback.js
export const testCallback = (callback) => {
callback();
};
export const testInstances = (callback) => {
new callback();
}
複製程式碼
再修改callback.test.js
:
//callback.test.js
import { testCallback, testInstances } from './callback.js';
test('測試 testInstances 方法', () => {
const fn = jest.fn();
testInstances(fn);
testInstances(fn);
console.info(fn.mock);
expect(fn).toBeCalled();
})
複製程式碼
執行callback.test.js
後,我們會發現終端,列印出以下.mock
屬性資訊:
{
calls: [ [], [] ],
instances: [ mockConstructor{}, mockConstructor{} ],
invocationCallOrder: [ 1, 2 ],
results: [
{ type: 'return', value: undefined },
{ type: 'return', value: undefined }
]
}
複製程式碼
我們可以發現instances
中變為了[ mockConstructor{}, mockConstructor{} ]
,也就是說instances
的確指示了jest.fn()
mock函式的this
指向。當mock函式當作普通函式呼叫時,this
指向undefined
;當mock函式當作建構函式被new
例項化時,this
指向mockConstructor{}
。
7.3、使用Mock
函式,改變內部函式的實現
在測試功能模組時,有時候我們想省略不想功能模組的某一執行步驟,不完全按照功能模組的程式碼邏輯執行,如果我們去改變功能模組的原始碼,那是不可取的,為此,我們可以藉助Mock
函式,改變內部函式的實現。
假設現在我們
建立mockFetch.js
:
//mockFetch.js
import axios from 'axios';
export const fetchData = () => {
return axios.get('https://juejin.im/editor').then(res => res.data);
}
複製程式碼
建立mockFetch.test.js
:
//mockFetch.test.js
import axios from 'axios';
import { fetchData } from './mockFetch.js';
jest.mock(axios);
test('測試 Mock 函式,改變內部函式的實現', () => {
axios.get.mockResolvedValue({ data: { success: true } });
return fetchData().then(data => {
expect(data).toEqual({ success: true });
})
});
複製程式碼
執行
npm run test
複製程式碼
我們會發現測試用例順利通過了。在上面程式碼中,我們使用jest.mock()
對axios
進行包裝處理並且使用mockResolvedValue
定義了axios.get
請求的資料為{ data: { success: true } }
,這樣就使得fetchData
不會真實地非同步請求'https://juejin.im/editor'
介面,而是同步地以{ data: { success: true }
為資料請求結果。
藉助jest.mock()
和mockResolvedValue
,我們就可以改變axios
模組本身的函式實現,使得該模組不會完全按照自身程式碼邏輯來執行。
7.4、建立__mocks__
資料夾,改變內部函式的實現
除了像7.3小節那樣使用jest.mock
函式,來改變內部函式的實現外,我們還可以藉助建立__mocks__
檔案,改變內部函式的實現。
假設現在我們繼續以mockFetch.js
為例,對7.3小節的檔案進行修改:
在mockFetch.js
同級目錄下建立__mocks__
資料夾,在該資料夾下建立mockFetch.js
,如下:
//__mocks__資料夾中的mockFetch.js
export const fetchData = () => {
console.log('__mocks__資料夾中的mockFetch.js 的fetchData執行');
const data = { success: true };
return Promise.resolve(data);
}
複製程式碼
修改mockFetch.js
:
//mockFetch.js
import axios from 'axios';
export const fetchData = () => {
console.log('mockFetch.js 的fetchData執行');
return axios.get('https://juejin.im/editor').then(res => res.data);
}
複製程式碼
修改mockFetch.test.js
:
//mockFetch.test.js
jest.mock('./mockFetch.js');
import { fetchData } from './mockFetch.js';
test('測試建立__mocks__資料夾,改變內部函式的實現', () => {
return fetchData().then(data => {
expect(data).toEqual({ success: true });
})
});
複製程式碼
執行
npm run test
複製程式碼
我們會發現測試用例順利通過了,檢視控制檯可以看到以下的資訊:
'__mocks__資料夾中的mockFetch.js 的fetchData執行'
複製程式碼
這樣就說明了,測試用例執行的是__mocks__
資料夾中的mockFetch.js
的fetchData
,之所以這樣,是因為在上面程式碼中,我們使用jest.mock('./mockFetch.js')
對./mockFetch.js
進行mock處理,使得import { fetchData } from './mockFetch.js';
中的./mockFetch.js
為我們建立的__mocks__
資料夾中的mockFetch.js
,通過這樣就藉助建立__mocks__
檔案,改變檔案的引用來改變內部函式的實現了。
八、Snapshot快照測試
我們在開發元件的過程中,往往需要為元件建立一份預設Props
配置,在元件升級迭代時,我們有可能會增加或修改預設Props
的配置,這樣導致我們可能會修改錯誤某些配置而我們沒有感知到,造成修改引入bug,為了避免這種情況,我們可以藉助Snapshot
生成檔案快照歷史記錄,以便在每次修改時進行修改提示,使開發者感知修改。
建立generateProps.js
進行Snapshot
說明:
//generateProps.js
export const generateProps = () => {
return {
name: 'jest',
time: '2020',
onChange: () => {}
}
}
複製程式碼
建立generateProps.test.js
:
//generateProps.test.js
import { generateProps } from './generateProps.js'
test('Snapshot快照測試', () => {
expect(generateProps()).toMatchSnapshot();
})
複製程式碼
首次執行
npm run test
複製程式碼
我們可以發現測試用例順利通過,並且會在當前檔案目錄中新增一個_snapshot_
資料夾,_snapshot_
資料夾就是generateProps()
返回值的一份快照
,記錄了當次generateProps()
執行的結果,以便作為下次變更的參照。
修改generateProps.js
:
//generateProps.js
export const generateProps = () => {
return {
name: 'jest',
time: '2020',
desc: '測試',
onChange: () => {}
}
}
複製程式碼
執行
npm run test
複製程式碼
我們會發現終端控制檯報錯:
1 snapshot failed
複製程式碼
這是因為修改後的檔案內容與快照不匹配,如果我們確定需要更新修改,那麼我們就要在控制檯終端進入Jest
命令列模式,輸入u
來確定更新快照。
九、測試Timer定時器
開發過程中的setTimeout
、setInterval()
等定時器都是非同步的,如果想測試它們,我們可以參照 三、Jest測試非同步程式碼。
不過如果定時器設定的時間過於大
的場景下,需要我們去等待定時器的觸發才可以知道測試結果的話,這明顯是不合理的,我們沒必要浪費時間去等待,Jest
也清楚這一點,所以,我們可以藉助jest.useFakeTimers()
以及jest.advanceTimersByTime()
來立即觸發
定時器,提高開發效率。
下面建立timer.js
,來說明如何測試定時器,:
// timer.js
export const timer = (callback) => {
setTimeout(() => {
callback()
}, 2000) ;
}
複製程式碼
建立timer.test.js
:
// timer.test.js
import { timer } from './timer.js';
jest.useFakeTimers();
test('測試定時器', () => {
const fn = jest.fn();
timer(fn);
jest.advanceTimersByTime(2000);
expect(fn).toHaveBeenCalledTimes(1);
});
複製程式碼
執行
npm run test
複製程式碼
我們可以發現測試用例不用等待定時器設定的時間
,就順利通過了。在這裡,我們使用jest.useFakeTimers()
來啟動假的定時器,然後藉助jest.advanceTimersByTime()
來快進2000
秒,所以相當於定時器已經成功觸發1
次了,符合測試期望值。
十、對DOM節點的測試
Jest
是執行在node
的環境的,理論上node
並沒有DOM
這個概念,Jest
為了方便開發者可以測試DOM
節點操作,Jest
自己在node
的環境下模擬了一套API
,我們可以稱它為JSDOM
。藉助JSDOM
特性,我們也可以在使用Jest
來測試DOM
節點。
下面以dom.js
以及jQuery
為例,來說明如何使用Jest
來測試DOM
節點:
執行
npm i jquery
複製程式碼
建立dom.js
:
//dom.js
import $ from 'jquery';
export const createDiv = () => {
$('body').append('<div/>')
}
複製程式碼
建立dom.test.js
:
//dom.test.js
import { createDiv }from './dom.js';
test(' 測試 DOM 節點 ', () => {
createDiv();
let length = $('body').find('div').length;
expect(length).toBe(1);
});
複製程式碼
執行
npm run test
複製程式碼
我們可以發現測試用例順利通過,也就是說,Jest
支援測試DOM
節點。