您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“前端Vue單元測(cè)試知識(shí)點(diǎn)有哪些”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
單元測(cè)試是用來測(cè)試項(xiàng)目中的一個(gè)模塊的功能,如函數(shù)、類、組件等。單元測(cè)試的作用有以下:
正確性:可以驗(yàn)證代碼的正確性,為上線前做更詳細(xì)的準(zhǔn)備;
自動(dòng)化:測(cè)試用例可以整合到代碼版本管理中,自動(dòng)執(zhí)行單元測(cè)試,避免每次手工操作;
解釋性:能夠?yàn)槠渌_發(fā)人員提供被測(cè)模塊的文檔參考,閱讀測(cè)試用例可能比文檔更完善;
驅(qū)動(dòng)開發(fā)、指導(dǎo)設(shè)計(jì):提前寫好的單元測(cè)試能夠指導(dǎo)開發(fā)的API設(shè)計(jì),也能夠提前發(fā)現(xiàn)設(shè)計(jì)中的問題;
保證重構(gòu):測(cè)試用例可以多次驗(yàn)證,當(dāng)需要回歸測(cè)試時(shí)能夠節(jié)省大量時(shí)間。
測(cè)試原則
測(cè)試代碼時(shí),只考慮測(cè)試,不考慮內(nèi)部實(shí)現(xiàn)
數(shù)據(jù)盡量模擬現(xiàn)實(shí),越靠近現(xiàn)實(shí)越好
充分考慮數(shù)據(jù)的邊界條件
對(duì)重點(diǎn)、復(fù)雜、核心代碼,重點(diǎn)測(cè)試
測(cè)試、功能開發(fā)相結(jié)合,有利于設(shè)計(jì)和代碼重構(gòu)
編寫步驟
準(zhǔn)備階段:構(gòu)造參數(shù),創(chuàng)建 spy 等
執(zhí)行階段:用構(gòu)造好的參數(shù)執(zhí)行被測(cè)試代碼
斷言階段:用實(shí)際得到的結(jié)果與期望的結(jié)果比較,以判斷該測(cè)試是否正常
清理階段:清理準(zhǔn)備階段對(duì)外部環(huán)境的影響,移除在準(zhǔn)備階段創(chuàng)建的 spy 等
單元測(cè)試的工具可分為三類:
測(cè)試運(yùn)行器(Test Runner):可以模擬各種瀏覽器環(huán)境,自定義配置測(cè)試框架和斷言庫(kù)等,如Karma.
測(cè)試框架:提供單元測(cè)試的功能模塊,常見的框架有Jest, mocha, Jasmine, QUnit.
工具庫(kù):assert, should.js, expect.js, chai.js等斷言庫(kù),enzyme渲染庫(kù),Istanbul覆蓋率計(jì)算。
這里,我們將使用 Jest 作為例子。Jest 功能全面,集成了各種工具,且配置簡(jiǎn)單,甚至零配置直接使用。
Jest 官網(wǎng)的描述是這樣的:
Jest is a delightful JavaScript Testing Framework with a focus on simplicity.
yarn add --dev jest # or # npm install -D jest
從官網(wǎng)提供的示例開始,測(cè)試一個(gè)函數(shù),這個(gè)函數(shù)完成兩個(gè)數(shù)字的相加,創(chuàng)建一個(gè) sum.js 文件︰
function sum(a, b) { return a + b; } module.exports = sum;
然后,創(chuàng)建 sum.test.js 文件︰
const sum = require('./sum'); test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); }); package.json 里增加一個(gè)測(cè)試任務(wù): { "scripts": { "test": "jest" } }
最后,運(yùn)行 yarn test 或 npm run test ,Jest將打印下面這個(gè)消息:
PASS ./sum.test.js
? adds 1 + 2 to equal 3 (5ms)
至此,完成了一個(gè)基本的單元測(cè)試。
注意:Jest 通過用 JSDOM 在 Node 虛擬瀏覽器環(huán)境模擬真實(shí)瀏覽器,由于是用 js 模擬 DOM, 所以 Jest 無(wú)法測(cè)試樣式 。Jest 測(cè)試運(yùn)行器自動(dòng)設(shè)置了 JSDOM。
你可以通過命令行直接運(yùn)行Jest(前提是jest已經(jīng)加到環(huán)境變量PATH中,例如通過 yarn global add jest 或 npm install jest --global 安裝的 Jest) ,并為其指定各種有用的配置項(xiàng)。如:
jest my-test --notify --config=config.json
Jest 命令有以下常見參數(shù):
--coverage 表示輸出單元測(cè)試覆蓋率,覆蓋率文件默認(rèn)在 tests/unit/coverage/lcov-report/index.html;
--watch 監(jiān)聽模式,與測(cè)試用例相關(guān)的文件更改時(shí)都會(huì)重新觸發(fā)單元測(cè)試。
更多選項(xiàng)查看Jest CLI Options.
使用 jest 命令可生成一個(gè)配置文件:
jest --init
過程中會(huì)有幾個(gè)選項(xiàng)供你選擇:
√ Would you like to use Typescript for the configuration file? ... no
√ Choose the test environment that will be used for testing ? jsdom (browser-like)
√ Do you want Jest to add coverage reports? ... yes
√ Which provider should be used to instrument code for coverage? ? babel
√ Automatically clear mock calls and instances between every test? ... yes
配置文件示例(不是基于上述選擇):
// jest.config.js const path = require('path') module.exports = { preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', rootDir: path.resolve(__dirname, './'), coverageDirectory: '<rootDir>/tests/unit/coverage', collectCoverageFrom: [ 'src/*.{js,ts,vue}', 'src/directives/*.{js,ts,vue}', 'src/filters/*.{js,ts,vue}', 'src/helper/*.{js,ts,vue}', 'src/views/**/*.{js,ts,vue}', 'src/services/*.{js,ts,vue}' ] }
yarn add --dev babel-jest @babel/core @babel/preset-env
可以在工程的根目錄下創(chuàng)建一個(gè)babel.config.js文件用于配置與你當(dāng)前Node版本兼容的Babel:
// babel.config.js module.exports = { presets: [['@babel/preset-env', {targets: {node: 'current'}}]], };
在項(xiàng)目中安裝 @vue/cli-plugin-unit-jest 插件,即可在 vue-cli 中使用 Jest:
vue add unit-jest # or # yarn add -D @vue/cli-plugin-unit-jest @types/jest
"scripts": { "test:unit": "vue-cli-service test:unit --coverage" },
@vue/cli-plugin-unit-jest 會(huì)在 vue-cli-service 中注入命令 test:unit,默認(rèn)會(huì)識(shí)別以下文件:<rootDir>/(tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)) 執(zhí)行單元測(cè)試,即 tests/unit 目錄下的 .spec.(js|jsx|ts|tsx) 結(jié)尾的文件及目錄名為 __tests__ 里的所有 js(x)/ts(x) 文件。
toBe() 檢查兩個(gè)基本類型是否精確匹配:
test('two plus two is four', () => { expect(2 + 2).toBe(4); });
toEqual() 檢查對(duì)象是否相等:
test('object assignment', () => { const data = {one: 1}; data['two'] = 2; expect(data).toEqual({one: 1, two: 2}); });
toBeNull 只匹配 null
toBeUndefined 只匹配 undefined
toBeDefined 與 toBeUndefined 相反
toBeTruthy 匹配任何 if 語(yǔ)句為真
toBeFalsy 匹配任何 if 語(yǔ)句為假
示例:
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(); });
test('two plus two', () => { const value = 2 + 2; expect(value).toBeGreaterThan(3); expect(value).toBeGreaterThanOrEqual(3.5); expect(value).toBeLessThan(5); expect(value).toBeLessThanOrEqual(4.5); // toBe and toEqual are equivalent for numbers expect(value).toBe(4); expect(value).toEqual(4); });
對(duì)于比較浮點(diǎn)數(shù)相等,使用 toBeCloseTo 而不是 toEqual,因?yàn)槟悴幌M麥y(cè)試取決于一個(gè)小小的舍入誤差。
test('兩個(gè)浮點(diǎn)數(shù)字相加', () => { const value = 0.1 + 0.2; //expect(value).toBe(0.3); 這句會(huì)報(bào)錯(cuò),因?yàn)楦↑c(diǎn)數(shù)有舍入誤差 expect(value).toBeCloseTo(0.3); // 這句可以運(yùn)行 });
可以使用正則表達(dá)式檢查:
test('there is no I in team', () => { expect('team').not.toMatch(/I/); }); test('but there is a "stop" in Christoph', () => { expect('Christoph').toMatch(/stop/); });
你可以通過 toContain 來檢查一個(gè)數(shù)組或可迭代對(duì)象是否包含某個(gè)特定項(xiàng):
const shoppingList = [ 'diapers', 'kleenex', 'trash bags', 'paper towels', 'milk', ]; test('the shopping list has milk on it', () => { expect(shoppingList).toContain('milk'); expect(new Set(shoppingList)).toContain('milk'); });
還可以用來檢查一個(gè)函數(shù)是否拋出異常:
function compileAndroidCode() { throw new Error('you are using the wrong JDK'); } test('compiling android goes as expected', () => { expect(() => compileAndroidCode()).toThrow(); expect(() => compileAndroidCode()).toThrow(Error); // You can also use the exact error message or a regexp expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK'); expect(() => compileAndroidCode()).toThrow(/JDK/); });
更多使用方法參考API文檔.
可使用 only() 方法表示只執(zhí)行這個(gè)test,減少不必要的重復(fù)測(cè)試:
test.only('it is raining', () => { expect(inchesOfRain()).toBeGreaterThan(0); }); test('it is not snowing', () => { expect(inchesOfSnow()).toBe(0); });
例如,假設(shè)您有一個(gè) fetchData(callback) 函數(shù),獲取一些數(shù)據(jù)并在完成時(shí)調(diào)用 callback(data)。 你期望返回的數(shù)據(jù)是一個(gè)字符串 'peanut butter':
test('the data is peanut butter', done => { function callback(data) { try { expect(data).toBe('peanut butter'); done(); } catch (error) { done(error); } } fetchData(callback); });
使用 done() 是為了標(biāo)識(shí)這個(gè) test 執(zhí)行完畢,如果沒有這個(gè) done(),在 test 執(zhí)行完畢后,我們的單元測(cè)試就結(jié)束了,這不符合我們的預(yù)期,因?yàn)閏allback還未調(diào)用,單元測(cè)試還沒走完。若 done() 函數(shù)從未被調(diào)用,將會(huì)提示超時(shí)錯(cuò)誤。
若 expect 執(zhí)行失敗,它會(huì)拋出一個(gè)錯(cuò)誤,后面的 done() 不再執(zhí)行。 若我們想知道測(cè)試用例為何失敗,我們必須將 expect 放入 try 中,將 error 傳遞給 catch 中的 done 函數(shù)。 否則,最后控制臺(tái)將顯示一個(gè)超時(shí)錯(cuò)誤失敗,不能顯示我們?cè)?expect(data) 中接收的值。
還是使用上面的例子:
test('the data is peanut butter', () => { return fetchData().then(data => { expect(data).toBe('peanut butter'); }); });
一定不要忘記 return 結(jié)果,這樣才能確保測(cè)試和功能同時(shí)結(jié)束。
如果是期望 Promise 被 reject, 則使用 catch 方法:
test('the fetch fails with an error', () => { expect.assertions(1); return fetchData().catch(e => expect(e).toMatch('error')); });
還可以使用 resolves 和 rejects 匹配器:
test('the data is peanut butter', () => { return expect(fetchData()).resolves.toBe('peanut butter'); }); test('the fetch fails with an error', () => { return expect(fetchData()).rejects.toMatch('error'); });
test('the data is peanut butter', async () => { const data = await fetchData(); expect(data).toBe('peanut butter'); }); test('the fetch fails with an error', async () => { expect.assertions(1); try { await fetchData(); } catch (e) { expect(e).toMatch('error'); } });
async/await 還可以和 resolves()/rejects() 結(jié)合使用:
test('the data is peanut butter', async () => { await expect(fetchData()).resolves.toBe('peanut butter'); }); test('the fetch fails with an error', async () => { await expect(fetchData()).rejects.toMatch('error'); });
在某些情況下,我們開始測(cè)試前需要做一些準(zhǔn)備工作,然后在測(cè)試完成后,要做一些清理工作,可以使用 beforeEach 和 afterEach。
例如,我們?cè)诿總€(gè)test前需要初始化一些城市數(shù)據(jù),test結(jié)束后要清理掉:
beforeEach(() => { initializeCityDatabase(); }); afterEach(() => { clearCityDatabase(); }); test('city database has Vienna', () => { expect(isCity('Vienna')).toBeTruthy(); }); test('city database has San Juan', () => { expect(isCity('San Juan')).toBeTruthy(); });
類似的還有 beforeAll 和 afterAll,在當(dāng)前spec測(cè)試文件開始前和結(jié)束后的單次執(zhí)行。
默認(rèn)情況下,before 和 after 的塊可以應(yīng)用到文件中的每個(gè)測(cè)試。 此外可以通過 describe 塊來將測(cè)試分組。 當(dāng) before 和 after 的塊在 describe 塊內(nèi)部時(shí),則其只適用于該 describe 塊內(nèi)的測(cè)試。
// Applies to all tests in this file beforeEach(() => { return initializeCityDatabase(); }); test('city database has Vienna', () => { expect(isCity('Vienna')).toBeTruthy(); }); test('city database has San Juan', () => { expect(isCity('San Juan')).toBeTruthy(); }); describe('matching cities to foods', () => { // Applies only to tests in this describe block beforeEach(() => { return initializeFoodDatabase(); }); test('Vienna <3 sausage', () => { expect(isValidCityFoodPair('Vienna', 'Wiener Würstchen')).toBe(true); }); test('San Juan <3 plantains', () => { expect(isValidCityFoodPair('San Juan', 'Mofongo')).toBe(true); }); });
由于使用了 describe 進(jìn)行分組,于是就有了嵌套的作用域,各生命周期的執(zhí)行順序如下:
外層作用域的 before 比內(nèi)層的先執(zhí)行,而 after 則相反;
同一層級(jí) beforeAll 比 beforeEach 先執(zhí)行,after 則相反;
beforeAll(() => console.log('1 - beforeAll')); afterAll(() => console.log('1 - afterAll')); beforeEach(() => console.log('1 - beforeEach')); afterEach(() => console.log('1 - afterEach')); test('', () => console.log('1 - test')); describe('Scoped / Nested block', () => { beforeAll(() => console.log('2 - beforeAll')); afterAll(() => console.log('2 - afterAll')); beforeEach(() => console.log('2 - beforeEach')); afterEach(() => console.log('2 - afterEach')); test('', () => console.log('2 - test')); }); // 1 - beforeAll // 1 - beforeEach // 1 - test // 1 - afterEach // 2 - beforeAll // 1 - beforeEach // 2 - beforeEach // 2 - test // 2 - afterEach // 1 - afterEach // 2 - afterAll // 1 - afterAll
jest.fn() 可以用來生成一個(gè) mock 函數(shù),jest 可以捕獲這個(gè)函數(shù)的調(diào)用、this、返回值等,這在測(cè)試回調(diào)函數(shù)時(shí)非常有用。
假設(shè)我們要測(cè)試函數(shù) forEach 的內(nèi)部實(shí)現(xiàn),這個(gè)函數(shù)為傳入的數(shù)組中的每個(gè)元素調(diào)用一次回調(diào)函數(shù)。
function forEach(items, callback) { for (let index = 0; index < items.length; index++) { callback(items[index]); } }
為了測(cè)試此函數(shù),我們可以使用一個(gè) mock 函數(shù),然后檢查 mock 函數(shù)的狀態(tài)來確保回調(diào)函數(shù)如期調(diào)用。
const mockCallback = jest.fn(x => 42 + x); forEach([0, 1], mockCallback); // 此 mock 函數(shù)被調(diào)用了兩次 expect(mockCallback.mock.calls.length).toBe(2); // 第一次調(diào)用函數(shù)時(shí)的第一個(gè)參數(shù)是 0 expect(mockCallback.mock.calls[0][0]).toBe(0); // 第二次調(diào)用函數(shù)時(shí)的第一個(gè)參數(shù)是 1 expect(mockCallback.mock.calls[1][0]).toBe(1); // 第一次函數(shù)調(diào)用的返回值是 42 expect(mockCallback.mock.results[0].value).toBe(42);
Mock 函數(shù)也可以用于在測(cè)試期間將測(cè)試值注入代碼︰
const myMock = jest.fn(); console.log(myMock()); // > undefined myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true); console.log(myMock(), myMock(), myMock(), myMock()); // > 10, 'x', true, true
假定有個(gè)從 API 獲取用戶的類。 該類用 axios 調(diào)用 API 然后返回 data,其中包含所有用戶的屬性:
// users.js import axios from 'axios'; class Users { static all() { return axios.get('/users.json').then(resp => resp.data); } } export default Users;
現(xiàn)在,為測(cè)試該方法而不實(shí)際調(diào)用 API (使測(cè)試緩慢與脆弱),我們可以用 jest.mock(...) 函數(shù)自動(dòng)模擬 axios 模塊。一旦模擬模塊,我們可為 .get 提供一個(gè) mockResolvedValue ,它會(huì)返回假數(shù)據(jù)用于測(cè)試。
// users.test.js import axios from 'axios'; import Users from './users'; jest.mock('axios'); test('should fetch users', () => { const users = [{name: 'Bob'}]; const resp = {data: users}; axios.get.mockResolvedValue(resp); // or you could use the following depending on your use case: // axios.get.mockImplementation(() => Promise.resolve(resp)) return Users.all().then(data => expect(data).toEqual(users)); });
有了mock功能,就可以給函數(shù)增加一些自定義匹配器:
// The mock function was called at least once expect(mockFunc).toHaveBeenCalled(); // The mock function was called at least once with the specified args expect(mockFunc).toHaveBeenCalledWith(arg1, arg2); // The last call to the mock function was called with the specified args expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2); // All calls and the name of the mock is written as a snapshot expect(mockFunc).toMatchSnapshot(); 也可以自己通過原生的匹配器模擬,下方的代碼與上方的等價(jià): // The mock function was called at least once expect(mockFunc.mock.calls.length).toBeGreaterThan(0); // The mock function was called at least once with the specified args expect(mockFunc.mock.calls).toContainEqual([arg1, arg2]); // The last call to the mock function was called with the specified args expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([ arg1, arg2, ]); // The first arg of the last call to the mock function was `42` // (note that there is no sugar helper for this specific of an assertion) expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42); // A snapshot will check that a mock was invoked the same number of times, // in the same order, with the same arguments. expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]); expect(mockFunc.getMockName()).toBe('a mock name');
官網(wǎng)是這樣介紹 Vue Test Utils 的:
Vue Test Utils 是 Vue.js 官方的單元測(cè)試實(shí)用工具庫(kù)。
以下的例子均基于 vue-cli 腳手架,包括 webpack/babel/vue-loader
Vue 的單文件組件在它們運(yùn)行于 Node 或?yàn)g覽器之前是需要預(yù)編譯的。我們推薦兩種方式完成編譯:通過一個(gè) Jest 預(yù)編譯器,或直接使用 webpack。這里我們選用 Jest 的方式。
yarn add -D jest @vue/test-utils vue-jest
vue-jest 目前并不支持 vue-loader 所有的功能,比如自定義塊和樣式加載。額外的,諸如代碼分隔等 webpack 特有的功能也是不支持的。如果要使用這些不支持的特性,你需要用 Mocha 取代 Jest 來運(yùn)行你的測(cè)試,同時(shí)用 webpack 來編譯你的組件。
vue-cli 中默認(rèn)使用 @ 作為 /src 的別名,在 Jest 也需要單獨(dú)配置:
// jest.config.js module.exports = { moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1' } }
被掛載的組件會(huì)返回到一個(gè)包裹器內(nèi),而包裹器會(huì)暴露很多封裝、遍歷和查詢其內(nèi)部的 Vue 組件實(shí)例的便捷的方法。
// test.js // 從測(cè)試實(shí)用工具集中導(dǎo)入 `mount()` 方法 // 同時(shí)導(dǎo)入你要測(cè)試的組件 import { mount } from '@vue/test-utils' import Counter from './counter' // 現(xiàn)在掛載組件,你便得到了這個(gè)包裹器 const wrapper = mount(Counter) // 你可以通過 `wrapper.vm` 訪問實(shí)際的 Vue 實(shí)例 const vm = wrapper.vm // 在控制臺(tái)將其記錄下來即可深度審閱包裹器 // 我們對(duì) Vue Test Utils 的探索也由此開始 console.log(wrapper)
在掛載的同時(shí),可以設(shè)置組件的各種屬性:
const wrapper = mount(Counter, { localVue, data() { return { bar: 'my-override' } }, propsData: { msg: 'abc' }, parentComponent: Foo, // 指定父組件 provide: { foo() { return 'fooValue' } } })
通過包裹器wrapper的相關(guān)方法,判斷組件渲染出來的HTML是否符合預(yù)期。
import { mount } from '@vue/test-utils' import Counter from './counter' describe('Counter', () => { // 現(xiàn)在掛載組件,你便得到了這個(gè)包裹器 const wrapper = mount(Counter) test('renders the correct markup', () => { expect(wrapper.html()).toContain('<span class="count">0</span>') }) // 也便于檢查已存在的元素 test('has a button', () => { expect(wrapper.contains('button')).toBe(true) }) })
當(dāng)用戶點(diǎn)擊按鈕的時(shí)候,我們的計(jì)數(shù)器應(yīng)該遞增。為了模擬這一行為,我們首先需要通過 wrapper.find() 定位該按鈕,此方法返回一個(gè)該按鈕元素的包裹器。然后我們能夠通過對(duì)該按鈕包裹器調(diào)用 .trigger() 來模擬點(diǎn)擊。
it('button click should increment the count', () => { expect(wrapper.vm.count).toBe(0) const button = wrapper.find('button') button.trigger('click') expect(wrapper.vm.count).toBe(1) })
為了測(cè)試計(jì)數(shù)器中的文本是否已經(jīng)更新,我們需要了解 nextTick。任何導(dǎo)致操作 DOM 的改變都應(yīng)該在斷言之前 await nextTick 函數(shù)。
it('button click should increment the count text', async () => { expect(wrapper.text()).toContain('0') const button = wrapper.find('button') await button.trigger('click') expect(wrapper.text()).toContain('1') })
每個(gè)掛載的包裹器都會(huì)通過其背后的 Vue 實(shí)例自動(dòng)記錄所有被觸發(fā)的事件。你可以用 wrapper.emitted() 方法取回這些事件記錄。
wrapper.vm.$emit('foo') wrapper.vm.$emit('foo', 123) /* `wrapper.emitted()` 返回以下對(duì)象: { foo: [[], [123]] } */
然后你可以基于這些數(shù)據(jù)來設(shè)置斷言:
// 斷言事件已經(jīng)被觸發(fā) expect(wrapper.emitted().foo).toBeTruthy() // 斷言事件的數(shù)量 expect(wrapper.emitted().foo.length).toBe(2) // 斷言事件的有效數(shù)據(jù) expect(wrapper.emitted().foo[1]).toEqual([123])
還可以觸發(fā)子組件的事件:
import { mount } from '@vue/test-utils' import ParentComponent from '@/components/ParentComponent' import ChildComponent from '@/components/ChildComponent' describe('ParentComponent', () => { test("displays 'Emitted!' when custom event is emitted", () => { const wrapper = mount(ParentComponent) wrapper.find(ChildComponent).vm.$emit('custom') expect(wrapper.html()).toContain('Emitted!') }) })
可以使用 setData() 或 setProps 設(shè)置組件的狀態(tài)數(shù)據(jù):
it('manipulates state', async () => { await wrapper.setData({ count: 10 }) await wrapper.setProps({ foo: 'bar' }) })
由于Vue Test Utils 的 setMethods() 即將廢棄,推薦使用 jest.spyOn() 方法來模擬Vue實(shí)例方法:
import MyComponent from '@/components/MyComponent.vue' describe('MyComponent', () => { it('click does something', async () => { const mockMethod = jest.spyOn(MyComponent.methods, 'doSomething') await shallowMount(MyComponent).find('button').trigger('click') expect(mockMethod).toHaveBeenCalled() }) })
如果你需要安裝所有 test 都使用的全局插件,可以使用 setupFiles,先在 jest.config.js 中指定 setup 文件:
// jest.config.js module.exports = { setupFiles: ['<rootDir>/tests/unit/setup.js'] }
然后在 setup.js 使用:
// setup.js import Vue from 'vue' // 以下全局注冊(cè)的插件在jest中不生效,必須使用localVue import ElementUI from 'element-ui' import VueClipboard from 'vue-clipboard2' Vue.use(ElementUI) Vue.use(VueClipboard) Vue.config.productionTip = false
當(dāng)你只是想在某些 test 中安裝全局插件時(shí),可以使用 localVue,這會(huì)創(chuàng)建一個(gè)臨時(shí)的Vue實(shí)例:
import { createLocalVue, mount } from '@vue/test-utils' // 創(chuàng)建一個(gè)擴(kuò)展的 `Vue` 構(gòu)造函數(shù) const localVue = createLocalVue() // 正常安裝插件 localVue.use(MyPlugin) // 在掛載選項(xiàng)中傳入 `localVue` mount(Component, { localVue })
假如我們有一個(gè)這樣的watcher:
watch: { inputValue(newVal, oldVal) { if (newVal.trim().length && newVal !== oldVal) { console.log(newVal) } } }
由于watch的調(diào)用是異步的,并且在下一個(gè)tick才會(huì)調(diào)用,因此可以通過檢測(cè)watcher里的方法是否被調(diào)用來檢測(cè)watch是否生效,使用 jest.spyOn() 方法:
describe('Form.test.js', () => { let cmp ... describe('Watchers - inputValue', () => { let spy beforeAll(() => { spy = jest.spyOn(console, 'log') }) afterEach(() => { spy.mockClear() }) it('is not called if value is empty (trimmed)', () => { }) it('is not called if values are the same', () => { }) it('is called with the new value in other cases', () => { }) }) }) it("is called with the new value in other cases", done => { cmp.vm.inputValue = "foo"; cmp.vm.$nextTick(() => { expect(spy).toBeCalled(); done(); }); });
當(dāng)我們使用一些第三方插件的時(shí)候,一般不需要關(guān)心其內(nèi)部的實(shí)現(xiàn),不需要測(cè)試其組件,可以使用 shallowMount 代替 mount, 減少不必要的渲染:
import { shallowMount } from '@vue/test-utils' const wrapper = shallowMount(Component) wrapper.vm // 掛載的 Vue 實(shí)例 還可以通過 findAllComponents 來查找第三方組件: import { Select } from 'element-ui' test('選中總部時(shí)不顯示分部和網(wǎng)點(diǎn)', async () => { await wrapper.setProps({ value: { clusterType: 'head-quarter-sit', branch: '', site: '' } }) // 總部不顯示分部和網(wǎng)點(diǎn) expect(wrapper.findAllComponents(Select)).toHaveLength(1) })
單元測(cè)試?yán)碚?/p>
單元測(cè)試能夠持續(xù)驗(yàn)證代碼的正確性、驅(qū)動(dòng)開發(fā),并起到一定的文檔作用;
測(cè)試時(shí)數(shù)據(jù)盡量模擬現(xiàn)實(shí),只考慮測(cè)試,不考慮內(nèi)部代碼;
測(cè)試時(shí)充分考慮數(shù)據(jù)的邊界條件
對(duì)重點(diǎn)、復(fù)雜、核心代碼,重點(diǎn)測(cè)試
編寫單元測(cè)試有以下階段:準(zhǔn)備階段、執(zhí)行階段、斷言階段、清理階段;
單元測(cè)試的工具可分為三類:測(cè)試運(yùn)行器(Test Runner)、測(cè)試框架、工具庫(kù)。
Jest
--watch 選項(xiàng)可以監(jiān)聽文件的編碼,自動(dòng)執(zhí)行單元測(cè)試;
測(cè)試異步代碼可以用 done 方法或 aync 函數(shù);
mock函數(shù)可以捕獲這個(gè)函數(shù)的調(diào)用、this、返回值等,測(cè)試回調(diào)函數(shù)時(shí)非常有用。
Vue Test Utils
用 mount 方法掛載組件,并可自定義各種vue屬性;
shallowMount 方法不渲染子組件,從而加快測(cè)試速度;
setupFiles 可以設(shè)置全局環(huán)境,如安裝 element-ui;
createLocalVue 可在創(chuàng)建單獨(dú)的vue實(shí)例,與全局的隔離;
“前端Vue單元測(cè)試知識(shí)點(diǎn)有哪些”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。