前言: 問了好些前端大神,回覆都說,作好前端測試是個不簡單的事情,特別是對於前端UI部分的測試。antd在UI上,能夠說作得很好,可是看了他們的相關測試,也沒有很完善。.javascript
目前來講,前端測試在service和model層的應用,結合相關資料和本身遇到的坑,給你們分享一下。前端
項目中,用到的 babel等編譯工具不是最新版本(6.0.0+),在當前最新版本24.0.0下,會報編譯工具不合適的錯誤,把版本降至20.0.0,可使用。具體版本,能夠根據項目中版本進行選擇。java
項目中的預編譯,主要是由babelrc 文件中配置,presets和plugins兩項,這兩項跟項目中,webpack中的配置保持一致node
presets: ['es2015', 'stage-3'],
plugins: ['transform-class-properties',
'syntax-dynamic-import',
'transform-runtime'
],
複製代碼
若是項目中在node_modules中依賴了未編譯的插件,須要進行下面的設置。不然,在.test用例中,沒法引入依賴,從而會報錯。webpack
transform: { // 將.js後綴的文件使用babel-jest處理
"^.+\\.js$": "babel-jest",
},
transformIgnorePatterns: // 未編譯的依賴,須要在這邊配置 ["<rootDir>/node_modules/(?!(snk-sse|javascript-state-machine))"],
複製代碼
"jest": "jest --coverage"
複製代碼
運行npm run jest,就會運行帶有.test.js的全部文件web
在node裏面,沒有實際接口調用,若是測試涉及到接口調用的返回數據,就須要進行模擬數據。下面,介紹其中一種比較簡單的方法:正則表達式
第一步,在 VideoService.js的同級目錄,新建 mocks 文件夾,而後在文件夾裏面,新建 VideoService.js(mock文件夾的文件名必須與的實際文件名一致)npm
// __mocks__/VideoService.js文件,
export default {
httpQuery() {
return new Promise((resolve)=>{
resolve(123123);
})
}
}
複製代碼
第二步,在測試test.js文件中,須要加上jest.mock(),RtVideoService.httpChange調用了VideoService.js的接口,這裏會查找__mocks__下面的文件,並使用VideoService模擬的數據數組
jest.mock('../services/VideoService.js'); // 必須存在,要否則會報錯
test('RtVideoService', () => {
const obj = {
newer: 'linwenhuan@sinosafe.com.cn',
callback: { error: jest.fn() }
};
await RtVideoService.httpChange(null, obj); // 使用了__mocks__下的文件
expect(obj.callback.next.mock.calls.length).toBe(1);
})
複製代碼
若是測試失敗,第一件要檢查的事就是,當僅運行這條測試時,它是否仍然失敗。 在 Jest 中很容易地只運行一個測試 — — 只需暫時將 test 命令更改成 test.only:bash
test.only('this will be the only test that runs', () => {
expect(true).toBe(false);
});
test('this test will not run', () => { // 此條不會運行
expect('A').toBe('A');
});
複製代碼
普通匹配器
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});
test('object assignment', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
});
複製代碼
布爾值匹配器
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();
});
複製代碼
字符串匹配器
數字匹配器
數組匹配器
自定義斷言
expect.extend({
toBeDivisibleBy(received, argument) {
const pass = received % argument == 0;
if (pass) {
return {
message: () =>
`expected ${received} not to be divisible by ${argument}`,
pass: true,
};
} else {
return {
message: () => `expected ${received} to be divisible by ${argument}`,
pass: false,
};
}
},
});
test('even and odd numbers', () => {
expect(100).toBeDivisibleBy(2);
expect(101).not.toBeDivisibleBy(2);
});
複製代碼
一、jest.fn()
// 舉例
test('測試jest.fn()調用', () => {
let mockFn = jest.fn();
let result = mockFn(1, 2, 3);
// 斷言mockFn的執行後返回undefined
expect(result).toBeUndefined();
// 斷言mockFn被調用
expect(mockFn).toBeCalled();
// 斷言mockFn被調用了一次
expect(mockFn).toBeCalledTimes(1);
// 斷言mockFn傳入的參數爲1, 2, 3
expect(mockFn).toHaveBeenCalledWith(1, 2, 3);
})
// 實際運用
test('answer', () => {
const fn = { error: jest.fn() };
ExeService.answer(null, fn);
expect(fn.error.mock.calls.length).toBe(1); // 測試成功
});
複製代碼
jest.fn()所建立的Mock函數還能夠設置返回值,定義內部實現或返回Promise對象。
test('測試jest.fn()返回固定值', () => {
let mockFn = jest.fn().mockReturnValue('default');
// 斷言mockFn執行後返回值爲default
expect(mockFn()).toBe('default');
})
test('測試jest.fn()內部實現', () => {
let mockFn = jest.fn((num1, num2) => {
return num1 * num2;
})
// 斷言mockFn執行後返回100
expect(mockFn(10, 10)).toBe(100);
})
test('測試jest.fn()返回Promise', async () => {
let mockFn = jest.fn().mockResolvedValue('default');
let result = await mockFn();
// 斷言mockFn經過await關鍵字執行後返回值爲default
expect(result).toBe('default');
// 斷言mockFn調用後返回的是Promise對象
expect(Object.prototype.toString.call(mockFn())).toBe("[object Promise]");
})
複製代碼
二、jest.mock(),改變函數的內部實現,上面已經有實際案例,不估重複講述
三、jest.spyOn()
jest.spyOn()方法一樣建立一個mock函數,可是該mock函數不只可以捕獲函數的調用狀況,還能夠正常的執行被spy的函數。實際上,jest.spyOn()是jest.fn()的語法糖,它建立了一個和被spy的函數具備相同內部代碼的mock函數
jest.mock('../services/VideoService.js'); // 必須存在,要否則會報錯
test('RtVideoService', () => {
const obj = {
newer: 'linwenhuan@sinosafe.com.cn',
callback: { error: jest.fn() }
};
await RtVideoService.httpChange(null, obj); // 使用了__mocks__下的文件
const spyFn = jest.spyOn(VideoService, 'httpChange');
expect(spyFn).toHaveBeenCalled();
expect(obj.callback.next.mock.calls.length).toBe(1);
})
複製代碼
在執行完寫的測試用例後,你可能會想,有什麼指標來講明我寫的測試用例,覆蓋項目全不全?
"test": "jest --coverage"
複製代碼
一、首先,在package的命令上,帶有coverage,這時,執行成功所有用例,會輸出如下結果:
%stmts是語句覆蓋率(statement coverage):是否是每一個語句都執行了?
%Branch分支覆蓋率(branch coverage):是否是每一個if代碼塊都執行了?
%Funcs函數覆蓋率(function coverage):是否是每一個函數都調用了?
%Lines行覆蓋率(line coverage):是否是每一行都執行了?
二、coverage 須要忽略的文件或文件夾
coveragePathIgnorePatterns: [
"\\\\node_modules\\\\",
"<rootDir>/src/utils/",
"<rootDir>/src/observers/",
"<rootDir>/lib/",
],
複製代碼
三、執行完測試用例後,會在項目要目錄,生成coverage文件夾,裏面輸出覆蓋率報告