概述
jest 是 facebook 開源的,用來進行單元測試的框架,可以測試 javascipt 和 react。 單元測試各種好處已經被說爛了,這裡就不多扯了。重點要說的是,使用 jest, 可以降低寫單元測試的難度。
單元測試做得好,能夠極大提高軟體的質量,加快軟體迭代更新的速度, 但是,單元測試也不是銀彈,單元測試做得好,並不是測試框架好就行,其實單元測試做的好不好,很大程度上取決於程式碼寫的是否易於測試。 單元測試不僅僅是測試程式碼,通過編寫單元測試 case,也可以反過來重構已有的程式碼,使之更加容易測試。
jest 的零配置思路是我最喜歡的特性。
安裝方式
# yarn
yarn add --dev jest
# OR npm
npm install --save-dev jest
測試方式
隨著 node.js 的發展,javascipt 語言的發展,前端可以勝任越來越多的工作,後端很多時候反而淪為資料庫的 http API 所以前端不僅僅是測試看得見的頁面,還有很多看不見的邏輯需要測試。
jest 提供了非常方便的 API,可以對下面的場景方便的測試
一般函式
對於一般的函式,參考附錄中的 jest Expect API 可以很容易的寫測試 case 待測試檔案:joinPath.js
const separator = '/'
const replace = new RegExp(separator + '{1,}', 'g')
export default (...parts) => parts.join(separator).replace(replace, separator)
測試檔案的命名:joinPath.test.js 採用這種命名,jest 會自動找到這個檔案來執行
import joinPath from 'utils/joinPath'
test('join path 01', () => {
const path1 = '/a/b/c'
const path2 = '/d/e/f'
expect(joinPath(path1, path2)).toBe('/a/b/c/d/e/f')
})
test('join path 02', () => {
const path1 = '/a/b/c/'
const path2 = '/d/e/f'
expect(joinPath(path1, path2)).toBe('/a/b/c/d/e/f')
})
test('join path 03', () => {
const path1 = '/a/b/c/'
const path2 = 'd/e/f'
expect(joinPath(path1, path2)).toBe('/a/b/c/d/e/f')
})
非同步函式
上面的是普通函式,對於非同步函式,比如 ajax 請求,測試寫法同樣容易 待測試檔案:utils/client.js
export const get = (url, headers = {}) => {
return fetch(url, {
method: 'GET',
headers: {
...getHeaders(),
...headers
}
}).then(parseResponse)
}
測試檔案:client.test.js
import { get } from 'utils/client'
test('fetch by get method', async () => {
expect.assertions(1)
// 測試使用了一個免費的線上 JSON API
const url = 'https://jsonip.com/'
const data = await get(url)
const { about } = data
expect(about).toBe('/about')
})
測試的生命週期
jest 測試提供了一些測試的生命週期 API,可以輔助我們在每個 case 的開始和結束做一些處理。 這樣,在進行一些和資料相關的測試時,可以在測試前準備一些資料,在測試後,清理測試資料。
4 個主要的生命週期函式:
- afterAll(fn, timeout): 當前檔案中的所有測試執行完成後執行 fn, 如果 fn 是 promise,jest 會等待 timeout 毫秒,預設 5000
- afterEach(fn, timeout): 每個 test 執行完後執行 fn,timeout 含義同上
- beforeAll(fn, timeout): 同 afterAll,不同之處在於在所有測試開始前執行
- beforeEach(fn, timeout): 同 afterEach,不同之處在於在每個測試開始前執行
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,還是儘量不要 mock,很多時候覺得程式碼不好測試而使用 mock,還不如看看如何重構程式碼,使之不用 mock 也能測試。 對於某些函式中包含的一些用時過長,或者呼叫第三方庫的地方,而這些地方並不是函式的主要功能, 那麼,可以用 mock 來模擬,從而提高測試執行的速度。
對於上面非同步函式的例子,我們改造成 mock 的方式:
const funcUseGet = async url => {
return await get(url)
}
test('mock fetch get method', async () => {
const client = require('utils/client')
client.get = jest.fn(url => ({ mock: 'test' }))
const url = 'http://mock.test'
const data = await funcUseGet(url)
expect(data).toEqual({ mock: 'test' })
})
react 測試
jest 可以測試 react component,但是我們用了狀態分離的方式開發前端, 大部分功能都在 action 中,這部分測試基本沒有做。
附錄
- jest Global API: https://facebook.github.io/jest/docs/en/api.html
- jest Expect API: https://facebook.github.io/jest/docs/en/expect.html
- jest Mock API: https://facebook.github.io/jest/docs/en/mock-function-api.html