關於前端單元測試的好處自沒必要說,基礎的介紹和知識能夠參考以前的博客連接:React Native單元測試。在軟件的測試領域,測試主要分爲:單元測試、集成測試和功能測試。前端
前端的測試框架有不少:mocha, jasmine, ava, testcafe, jest,他們都有各自擅長的領域和特色,而咱們採用的jest框架具備以下的一些特色:react
# yarn
yarn add --dev jest
# npm
npm install --save-dev jest
複製代碼
咱們編寫一個被測試文件的sum.js,代碼以下:git
function sum(a, b) {
return a + b;
}
module.exports = sum;
複製代碼
而後,咱們添加一個名爲sum.test.js的測試文件,注意命名時遵循xxx.test.js的命名規則。es6
const sum = require(‘./sum'); test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); }); 複製代碼
「斷言」一般是給程序開發人員本身使用,而且在開發測試期間使用,用於判斷在某些邏輯條件下會執行某種預期的結果。Jest框架內置了豐富的斷言語句,詳細的能夠參考Jest 的Expect。此處,列舉一些經常使用的:github
.toBe(value)
.toHaveBeenCalled()
.toBeFalsy()
.toEqual(value)
.toBeGreaterThan(number)
.toBeGreaterThanOrEqual(number)
複製代碼
舉個例子,下面是一個被測試的文件Hook.js。npm
export default class Hook {
constructor() {
this.init();
}
init() {
this.a = 1;
this.b = 1;
}
sum() {
return this.a + this.b;
}
}
複製代碼
Hook.js主要實現兩個數字相加的功能,而後咱們編寫一個測試文件Hook.test.js。編程
import Hook from '../src/hook';
describe('hook', () => {
const hook = new Hook;
// 每一個測試用例執行前都會還原數據,因此下面兩個測試能夠經過。
beforeEach( () => {
hook.init();
})
test('test hook 1', () => {
hook.a = 2;
hook.b = 2;
expect(hook.sum()).toBe(4);
})
test('test hook 2', () => {
expect(hook.sum()).toBe(2);// 測試經過
})
})
複製代碼
而後,在控制檯執行yarn jest命令,便可運行單元測試,執行完成後會給出相應的結果。例如: api
jest 測試提供了一些測試的生命週期 API,能夠輔助咱們在每一個 case 的開始和結束作一些處理。 這樣,在進行一些和數據相關的測試時,能夠在測試前準備一些數據,在測試完成後清理測試數據。這部分的知識能夠參考官方的全局API。promise
這裏列舉4個主要的生命週期勾子:瀏覽器
BeforeAll(() => {
console.log('before all tests to excute !')
})
BeforeEach(() => {
console.log('before each test !')
})
AfterAll(() => {
console.log('after all tests to excute !')
})
AfterEach(() => {
console.log('after each test !')
})
Test('test lifecycle 01', () => {
expect(1 + 2).toBe(3)
})
Test('test lifecycle 03', () => {
expect(2 + 2).toBe(4)
})
複製代碼
mock測試就是在測試過程當中,對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來建立以便繼續進行測試的測試方法。Mock函數一般會提供如下三種特性:
jest.fn()是建立Mock函數最簡單的方式,若是沒有定義函數內部的實現,jest.fn()會返回undefined做爲返回值。例如:有兩個被測試代碼every.js和foreach.js。代碼以下: every.js
function every(array, predicate) {
let index = -1
const length = array == null ? 0 : array.length
while (++index < length) {
if (!predicate(array[index], index, array)) {
return false
}
}
return true
}
export default every
複製代碼
foreach.js
function foreach(arr, fn) {
for(let i = 0, len = arr.length; i < len; i++) {
fn(arr[i]);
}
}
module.exports = foreach;
複製代碼
下面是測試用例mock.test.js文件的代碼:
import foreach from '../foreach';
import every from '../every';
describe('mock test', () => {
it('test foreach use mock', () => {
// 經過jest.fn()生成一個mock函數
const fn = jest.fn();
foreach([1, 2, 3], fn);
//測試mock函數被調用了3次
expect(fn.mock.calls.length).toBe(3);
// 測試第二次調用的函數第一個參數是3
expect(fn.mock.calls[2][0]).toBe(3);
})
it('test every use mock return value', () => {
const fn = jest.fn();
fn
.mockReturnValueOnce(true)
.mockReturnValueOnce(false);
const res = every([1, 2, 3, 4], fn);
expect(fn.mock.calls.length).toBe(2);
expect(fn.mock.calls[1][1]).toBe(1);
})
it('test every use mock mockImplementationOnce', () => {
const fn = jest.fn((val, index) => {
if (index == 2) {
return false;
}
return true;
});
const res = every([1, 2, 3, 4], fn);
expect(fn.mock.calls.length).toBe(3);
expect(fn.mock.calls[1][1]).toBe(1);
})
})
複製代碼
測試代碼時能夠忽略模塊的依存關係,進行手動mock。例如,有一個測試文件sum2.js。
function sum2(a, b) {
if (a > 10) return a * b;
return a + b;
}
export default sum2;
複製代碼
若是要mock 一個sum2.js 文件的話,須要在sum2.js 同級目錄下新建文件夾__mock__,而後在此文件下新建文件同名 sum2.js,而後mock返回100。
export default function sum2(a, b) {
return 100;
}
複製代碼
而後,新建一個mock_file.test.js測試文件。
jest.mock('../sum2');
import sum2 from '../__mock__/sum2';
it('test mock sum2', () => {
//由於此時訪問的是__mock__文件夾下的sum2.js因此測試經過
expect(sum2(1, 11111)).toBe(100);
})
複製代碼
在實際開發過程當中,常常會遇到一些異步的JavaScript代碼。當有異步方式運行的代碼的時候,Jest須要知道當前它測試的代碼是否已經完成,而後它才能夠轉移動另外一個測試中,也就是說,測試的用例必定要在測試對象結束以後纔可以運行。Jest的異步測試主要分爲3種:
done的例子以下:
function fetchData(call) {
setTimeout(() => {
call('peanut butter1')
},1000);
}
test('the data is peanut butter', (done) => {
function callback(data) {
expect(data).toBe('peanut butter');
done()
}
fetchData(callback);
});
複製代碼
由於superagent庫支持 promise和async/await方式,因此用superagent舉例,實際項目開發可能會涉及到promise(es6之前的寫法)和async/await(最新的寫法),你們能夠根據實際狀況編寫測試代碼。
import superagent from 'superagent';
const target = 'http://www.baidu.com';
describe('test promise async', () => {
it('test done', done => {
superagent.get(target).end((err, res) => {
expect(res).toBeTruthy();
done();
});
});
it('test promise', () => {
return superagent.get(target).then((res) => {
expect(res).toBeTruthy();
});
});
it('test async/await', async () => {
const res = await superagent.get(target);
expect(res).toBeTruthy();
});
});
複製代碼
注意,使用superagent框架進行異步測試時,請確保你的項目安裝了superagent依賴。
快照測試第一次運行的時候會將被測試ui組件在不一樣狀況下的渲染結果保存一份快照文件,後面每次再運行快照測試時,都會和第一次的比較,除非執行「yarn test – -u」命令刪除快照文件。例如,有一個文件reactComp.js.
import React from 'react';
export default class reactComp extends React.Component {
render() {
return (
<div>我是react組件 </div>
)
}
}
複製代碼
而後,編寫一個測試用例文件reactComp.test.js。
import React from 'react';
import renderer from 'react-test-renderer';
import RC from '../reactComp';
test('react-comp snapshot test', () => {
const component = renderer.create(<RC />);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
})
test('react-comp snapshot test2', () => {
const component = renderer.create(<RC />);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
})
複製代碼
執行測試命令,會在test目錄下生成一個__snapshots__目錄,在此目錄下會與一個快照文件,格式以下:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`react-comp snapshot test 1`] = `
<div>
我是react組件
</div>
`;
exports[`react-comp snapshot test2 1`] = `
<div>
我是react組件
</div>
`;
複製代碼
若是被測試代碼有正常更新,可使用「jest --updateSnapshot 」命令從新更新緩存文件。