npm install -D @babel/core @babel/preset-env
複製代碼
.babelrcjavascript
{
"presets": [
[
"@babel/preset-env", {
"targets": {
"node": "current"
}
}
]
]
}
複製代碼
jest 在運行前會檢查是否安裝 babel,若是安裝了會去取 .babelrc 文件,結合 babel 將代碼進行轉化,運行轉化後的代碼。 3. jest 默認配置css
npx jest --init
複製代碼
git
結合使用,會比較現有文件和 commit 的文件的差別,只測試差別文件const throwError = () => {
throw new Error('error')
}
it('can throw error', () => {
expect(throwError).toThrow('error') // 判斷throw函數能夠拋出異常,異常信息爲 "error"。也能夠寫正則
})
複製代碼
這裏有個小技巧:當咱們想忽略掉單個文件中的其餘測試用例,只針對一個測試用例作調試的時候,能夠加上 .only
html
it.only('test', () => {
// ...
})
複製代碼
但這並不會忽略其餘測試文件的測試用例前端
這裏有三個異步方法,對這三個方法進行代碼測試,"www.dell-lee.com/react/api/d…" 會返回 {success: true}, "www.dell-lee.com/react/api/4…" 則不存在。vue
import axios from 'axios'
export function getData1() {
return axios.get('http://www.dell-lee.com/react/api/demo.json')
}
export function getData2(fn) {
axios.get('http://www.dell-lee.com/react/api/demo.json').then(res => {
fn(res)
})
}
export function get404() {
return axios.get('http://www.dell-lee.com/react/api/404.json')
}
複製代碼
對於異步代碼測試,時機很重要,必須保證咱們的測試用例在異步代碼走完以後才結束。有如下幾種辦法:java
import {getData1, getData2, get404} from './fetchData/fetchData'
it('getData1 方法1', (done) => {
getData1().then(res => {
expect(res.data).toEqual({
success: true
})
done() // 若是不加 done,還沒執行到 .then 方法,測試用例已經結束了
})
})
it('getData1 方法2', () => {
return getData1().then(res => {
expect(res.data).toEqual({
success: true
})
})
})
it('getData2 方法2', (done) => {
getData2((res) => {
expect(res.data).toEqual({
success: true
})
done()
})
})
it('getData1 方法3', async () => {
const res = await getData1()
expect(res.data).toEqual({
success: true
})
})
/*********** 重點關注 ***********/
it('get404', (done) => {
expect.assertions(1)
get404().catch(r => {
expect(r.toString()).toMatch('404')
done()
})
})
複製代碼
重點講一下上面的最後一個測試用例,假設咱們如今有一個返回的是 404 的接口,咱們須要對這個接口測試,指望他返回 404。 咱們用 catch 捕獲,在 catch 中判斷。node
可是,假如這個接口返回的不是 404,而是正常返回 200,這個 catch 則不會執行,expect 也不會執行,測試依然是經過的。這不符合咱們的預期!因此,咱們須要加上 expect.assertions(1)
進行斷言:下面必定會執行一個 expectreact
固然,也能夠用 async await 方法進行 404 接口的測試ios
it('get404 方法3', async () => {
await expect(get404()).rejects.toThrow()
})
複製代碼
前四個鉤子使用起來很簡單,調用方法以下:git
beforeAll(() => {
// ...
})
複製代碼
若是測試先後要作一些處理,儘量寫在這些鉤子函數中,他能保證必定的執行順序。
describe 能夠用來進行用例分組,爲了讓咱們的測試輸出結果更好看,更有層次。 同時,在每一個 describe 中都有上面 4 個鉤子函數的存在,咱們來看看具體的狀況:
describe('測試 Button 組件', () => {
beforeAll(...) // 1
beforeEach(...) // 2
afterEach(...) // 3
afterAll(...) // 4
describe('測試 Button 組件的事件', () => {
beforeAll(...) // 5
beforeEach(...) // 6
afterEach(...) // 7
afterAll(...) // 8
it('event1', ()=>{...})
})
})
複製代碼
上面鉤子函數的執行順序是: 1 > 5 > 2 > 6 > 3 > 7 > 4 > 8
外部的鉤子函數對 describe 內部的用例也生效,執行順序爲:先外部後內部
前面提到了能夠測試異步代碼,對於一些接口都能進行請求測試。但假如每個接口都真的發起請求,那一次測試須要耗費的時間是不少的。 這時候咱們能夠模擬請求方法,步驟以下:
import axios from 'axios'
export function getData() {
return axios.get('http://www.dell-lee.com/react/api/demo.json')
}
複製代碼
export function getData() {
return Promise.resolve({
success: true
})
}
複製代碼
jest.mock('./mock/mock.js') // 聲明下面引入的 getData 方法是 jest 模擬的,若是不須要引入該方法則不須要聲明
import {getData} from './mock/mock.js' // 導入 mock.js,但實際上 jest 會導入 __mocks__ 下的 mock.js
test('mock 方法測試', () => {
getData().then(data => {
expect(data).toEqual({
success: true
})
done()
})
})
複製代碼
除了上面的這種辦法,還能在 jest.config.js 中配置自動開啓 mock,這樣 jest 會自動去查找當前文件同級有沒有 mock 文件夾,裏面有沒有對應文件
module.exports = {
automock: true
}
複製代碼
講了兩種 mock 的方法,還有一種極端狀況須要避免 mock:
咱們在 mock.js 中定義了一個須要 mock 的 getData 方法,又另外定義了一個不須要 mock 的普通方法,當咱們在測試文件導入的時候,須要避免 jest 去 mocks/mock.js 下找這個普通方法,這裏須要用 jest 提供的方法導入:
const { regularMethod } = jest.requireActual('./mock/mock.js')
複製代碼
當咱們有以下代碼須要測試的時候:
export default (fn) => {
setTimeout(() => {
fn()
}, 3000)
}
複製代碼
咱們不可能老是去等待定時器,這時候咱們要用 Jest 來操做時間!步驟以下:
jest.useFakeTimers()
使用 jest 「自制的」 定時器,這裏放在 beforeEach 裏面是由於快進時間可能被調用屢次,我但願在每一個測試用例裏,這個時鐘都是初始狀態,不會互相影響。jest.advanceTimersByTime(3000)
,這個方法能夠調用任意次,快進的時間會疊加。特別說明一下:jest.fn() 生成的是一個函數,這個函數能被監聽調用過幾回。
import timer from './timer/timer'
beforeEach(() => {
jest.useFakeTimers()
})
it('timer 測試', () => {
const fn = jest.fn()
timer(fn)
jest.advanceTimersByTime(3000)
expect(fn).toHaveBeenCalledTimes(1)
})
複製代碼
一樣的,當咱們只關注類的方法是否被調用,而不關心方法調用產生的結果時,能夠 mock 類
在 util/util.js 中定義了 Util 類
export class Util {
a() {}
b() {}
}
複製代碼
在 util/useUtil 中調用了這個類
import {Util} from './util'
export function useUtil() {
let u = new Util()
u.a()
u.b()
}
複製代碼
咱們須要測試 u.a 和 u.b 被調用,jest.mock('./util/util')
會將 Util、Util.a、Util.b 都 mock 成 jest.fn
測試用例以下:
jest.mock('./util/util') // mock Util 類
import {Util} from './util/util'
import {useUtil} from './util/uesUtil'
test('util 的實例方法被執行了', () => {
useUtil()
expect(Util).toHaveBeenCalled()
expect(Util.mock.instances[0].a).toHaveBeenCalled()
expect(Util.mock.instances[0].b).toHaveBeenCalled()
})
複製代碼
Vue 提供了 @vue/test-utils 來幫助咱們進行單元測試,建立 Vue 項目的時候勾選測試選項會自動幫咱們安裝。
先來介紹兩個經常使用的掛載方法:
再來看一個簡單的測試用例:
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
expect(wrapper.props('msg')).toBe(msg)
})
})
複製代碼
shallowMount 會返回一個 wrapper,這個 wrapper 上面會包含不少幫助咱們測試的方法,詳見
快照測試的意思是,會將組件像拍照同樣拍下來,存底。下次運行測試用例的時候,若是組件發生變化,和快照不同了,就會報錯。
測試用例寫法以下: 第一次測試會保存 wrapper 的快照,第二次會比較當前 wrapper 和快照的區別
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
expect(wrapper).toMatchSnapshot()
})
})
複製代碼
咱們再來看看快照長什麼樣子:
當快照發生變化時,咱們能夠在終端按 u
進行更新快照
覆蓋率測試是對測試徹底程度的一個評估,測試覆蓋到的業務代碼越多,覆蓋率越高。
在 jest.config.js 中咱們能夠設置 collectCoverageFrom
,來設置須要進行覆蓋率測試的文件,這裏咱們測試一下全部的 .vue
文件,忽略 node_modules
下全部文件。
要注意,在 Vue 中配置 jest,參考文檔
而後添加一條 script 命令,就能進行測試了:
"test:unit": "vue-cli-service test:unit --coverage"
複製代碼
執行命令會生成 coverage
文件夾,Icov-report/index.html
裏會可視化展現咱們的測試覆蓋率
若是咱們在組件中引入了 Vuex 狀態或者使用了相關方法,在測試用例裏,掛載組件的時候只須要傳入 vuex 的 store 便可。
import store from '@/store/index'
const wrapper = mount(HelloWorld, {
store
})
複製代碼
就拿 shallowMount
來講,這個 api 就很適合單元測試,單元測試不關注單元之間的聯繫,對每一個單元進行獨立測試, 這也使得它代碼量大,測試間過於獨立。在進行一些函數庫的測試,各個函數比較獨立的時候,就很適合單元測試。
在進行一些業務組件測試時,須要關注組件間的聯繫,比較適合用集成測試。
TDD:測試驅動開發,先寫測試用例,而後根據用例寫代碼,比較關注代碼自己。以下:
describe('input 輸入回車,向外觸發事件,data 中的 inputValue 被賦值', () => {
const wrapper = shallowMount(TodoList)
const inputEle = wrapper.find('input').at(0)
const inputContent = '用戶輸入內容'
inputEle.setValue(inputContent)
// expect:add 事件被 emit
except(wrapper.emitted().add).toBeTruthy()
// expect:data 中的 inputValue 被賦值爲 inputContent
except(wrapper.vm.inputValue).toBe(inputContent)
})
複製代碼
TDD 關注代碼內部如何實現,關注事件是否觸發?屬性是否設置?data 數據是否被更新?
BDD:用戶行爲驅動開發,先寫完業務代碼,而後站在用戶的角度去測試功能,不關注代碼實現過程,只是經過模擬用戶操做測試功能。
好比下面這個用例:
describe('TodoList 測試', () => {
it(` 1. 用戶在 header 輸入框輸入內容 2. 鍵盤迴車 3. 列表項增長一項,內容爲用戶輸入內容 `, () => {
// 掛載 TodoList 組件
const wrapper = mount(TodoList)
// 模擬用戶輸入
const inputEle = wrapper.find('input').at(0)
const inputContent = '用戶輸入內容'
inputEle.setValue(inputContent)
// 模擬觸發的事件
inputEle.trigger('content')
inputEle.trigger('keyup.enter')
// expect:列表項增長對應內容
const listItems = wrapper.find('.list-item')
expect(listItems.length).toBe(1) // 增長 1 項
expect(listItems.at(0).text()).toContain(inputContent) // 增長 1 項
})
})
複製代碼
參考: