欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

前端Vue單元測試入門教程

 更新時間:2021年12月20日 15:19:10   作者:Da_Ming  
單元測試是用來測試項目中的一個模塊的功能,本文主要介紹了前端Vue單元測試入門教程,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下

一、為什么需要單元測試

單元測試是用來測試項目中的一個模塊的功能,如函數(shù)、類、組件等。單元測試的作用有以下:

  • 正確性:可以驗證代碼的正確性,為上線前做更詳細(xì)的準(zhǔn)備;
  • 自動化:測試用例可以整合到代碼版本管理中,自動執(zhí)行單元測試,避免每次手工操作;
  • 解釋性:能夠為其他開發(fā)人員提供被測模塊的文檔參考,閱讀測試用例可能比文檔更完善;
  • 驅(qū)動開發(fā)、指導(dǎo)設(shè)計:提前寫好的單元測試能夠指導(dǎo)開發(fā)的API設(shè)計,也能夠提前發(fā)現(xiàn)設(shè)計中的問題;
  • 保證重構(gòu):測試用例可以多次驗證,當(dāng)需要回歸測試時能夠節(jié)省大量時間。

二、如何寫單元測試

測試原則

  • 測試代碼時,只考慮測試,不考慮內(nèi)部實現(xiàn)
  • 數(shù)據(jù)盡量模擬現(xiàn)實,越靠近現(xiàn)實越好
  • 充分考慮數(shù)據(jù)的邊界條件
  • 對重點、復(fù)雜、核心代碼,重點測試
  • 測試、功能開發(fā)相結(jié)合,有利于設(shè)計和代碼重構(gòu)

編寫步驟

  • 準(zhǔn)備階段:構(gòu)造參數(shù),創(chuàng)建 spy 等
  • 執(zhí)行階段:用構(gòu)造好的參數(shù)執(zhí)行被測試代碼
  • 斷言階段:用實際得到的結(jié)果與期望的結(jié)果比較,以判斷該測試是否正常
  • 清理階段:清理準(zhǔn)備階段對外部環(huán)境的影響,移除在準(zhǔn)備階段創(chuàng)建的 spy 等

三、測試工具

單元測試的工具可分為三類:

  • 測試運行器(Test Runner):可以模擬各種瀏覽器環(huán)境,自定義配置測試框架和斷言庫等,如Karma.
  • 測試框架:提供單元測試的功能模塊,常見的框架有Jest, mocha, Jasmine, QUnit.
  • 工具庫:assert, should.js, expect.js, chai.js等斷言庫,enzyme渲染庫,Istanbul覆蓋率計算。

這里,我們將使用 Jest 作為例子。Jest 功能全面,集成了各種工具,且配置簡單,甚至零配置直接使用。

四、Jest入門

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)提供的示例開始,測試一個函數(shù),這個函數(shù)完成兩個數(shù)字的相加,創(chuàng)建一個 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 里增加一個測試任務(wù):
{
  "scripts": {
    "test": "jest"
  }
}

最后,運行 yarn test 或 npm run test ,Jest將打印下面這個消息:

PASS? ./sum.test.js
? adds 1 + 2 to equal 3 (5ms)

至此,完成了一個基本的單元測試。

注意:Jest 通過用 JSDOM 在 Node 虛擬瀏覽器環(huán)境模擬真實瀏覽器,由于是用 js 模擬 DOM, 所以 Jest 無法測試樣式 。Jest 測試運行器自動設(shè)置了 JSDOM。

Jest Cli

你可以通過命令行直接運行Jest(前提是jest已經(jīng)加到環(huán)境變量PATH中,例如通過 yarn global add jest 或 npm install jest --global 安裝的 Jest) ,并為其指定各種有用的配置項。如:

jest my-test --notify --config=config.json

Jest 命令有以下常見參數(shù):

  • --coverage 表示輸出單元測試覆蓋率,覆蓋率文件默認(rèn)在 tests/unit/coverage/lcov-report/index.html;
  • --watch 監(jiān)聽模式,與測試用例相關(guān)的文件更改時都會重新觸發(fā)單元測試。

更多選項查看Jest CLI Options.

使用配置文件

使用 jest 命令可生成一個配置文件:

jest --init

過程中會有幾個選項供你選擇:

√ 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}'
    ]
}

使用 Babel

yarn add --dev babel-jest @babel/core @babel/preset-env

可以在工程的根目錄下創(chuàng)建一個babel.config.js文件用于配置與你當(dāng)前Node版本兼容的Babel:

// babel.config.js
module.exports = {
  presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
};

vue-cli 中使用 Jest

在項目中安裝 @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 會在 vue-cli-service 中注入命令 test:unit,默認(rèn)會識別以下文件:<rootDir>/(tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)) 執(zhí)行單元測試,即 tests/unit 目錄下的 .spec.(js|jsx|ts|tsx) 結(jié)尾的文件及目錄名為 __tests__ 里的所有 js(x)/ts(x) 文件。

常見示例

判斷值相等

toBe() 檢查兩個基本類型是否精確匹配:

test('two plus two is four', () => {
  expect(2 + 2).toBe(4);
});

toEqual() 檢查對象是否相等:

test('object assignment', () => {
  const data = {one: 1};
  data['two'] = 2;
  expect(data).toEqual({one: 1, two: 2});
});

檢查類false值

  • toBeNull 只匹配 null
  • toBeUndefined 只匹配 undefined
  • toBeDefined 與 toBeUndefined 相反
  • toBeTruthy 匹配任何 if 語句為真
  • toBeFalsy 匹配任何 if 語句為假

示例:

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();
});

數(shù)字大小比較

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);
});

對于比較浮點數(shù)相等,使用 toBeCloseTo 而不是 toEqual,因為你不希望測試取決于一個小小的舍入誤差。

test('兩個浮點數(shù)字相加', () => {
  const value = 0.1 + 0.2;
  //expect(value).toBe(0.3);           這句會報錯,因為浮點數(shù)有舍入誤差
  expect(value).toBeCloseTo(0.3); // 這句可以運行
});

字符串比較

可以使用正則表達(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/);
});

數(shù)組和類數(shù)組

你可以通過 toContain 來檢查一個數(shù)組或可迭代對象是否包含某個特定項:

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');
});

異常

還可以用來檢查一個函數(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文檔.

只執(zhí)行當(dāng)前test

可使用 only() 方法表示只執(zhí)行這個test,減少不必要的重復(fù)測試:

test.only('it is raining', () => {
  expect(inchesOfRain()).toBeGreaterThan(0);
});

test('it is not snowing', () => {
  expect(inchesOfSnow()).toBe(0);
});

測試異步代碼

回調(diào)函數(shù)

例如,假設(shè)您有一個 fetchData(callback) 函數(shù),獲取一些數(shù)據(jù)并在完成時調(diào)用 callback(data)。 你期望返回的數(shù)據(jù)是一個字符串 '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)識這個 test 執(zhí)行完畢,如果沒有這個 done(),在 test 執(zhí)行完畢后,我們的單元測試就結(jié)束了,這不符合我們的預(yù)期,因為callback還未調(diào)用,單元測試還沒走完。若 done() 函數(shù)從未被調(diào)用,將會提示超時錯誤。

若 expect 執(zhí)行失敗,它會拋出一個錯誤,后面的 done() 不再執(zhí)行。 若我們想知道測試用例為何失敗,我們必須將 expect 放入 try 中,將 error 傳遞給 catch 中的 done 函數(shù)。 否則,最后控制臺將顯示一個超時錯誤失敗,不能顯示我們在 expect(data) 中接收的值。

Promises

還是使用上面的例子:

test('the data is peanut butter', () => {
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');
  });
});

一定不要忘記 return 結(jié)果,這樣才能確保測試和功能同時結(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');
});

Async/Await

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');
});

安裝和拆卸

測試前和測試后

在某些情況下,我們開始測試前需要做一些準(zhǔn)備工作,然后在測試完成后,要做一些清理工作,可以使用 beforeEach 和 afterEach。
例如,我們在每個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測試文件開始前和結(jié)束后的單次執(zhí)行。

測試用例分組

默認(rèn)情況下,before 和 after 的塊可以應(yīng)用到文件中的每個測試。 此外可以通過 describe 塊來將測試分組。 當(dāng) before 和 after 的塊在 describe 塊內(nèi)部時,則其只適用于該 describe 塊內(nèi)的測試。

// 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);
  });
});

執(zhí)行順序

由于使用了 describe 進(jìn)行分組,于是就有了嵌套的作用域,各生命周期的執(zhí)行順序如下:

  • 外層作用域的 before 比內(nèi)層的先執(zhí)行,而 after 則相反;
  • 同一層級 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

mock 函數(shù)

jest.fn() 可以用來生成一個 mock 函數(shù),jest 可以捕獲這個函數(shù)的調(diào)用、this、返回值等,這在測試回調(diào)函數(shù)時非常有用。

測試mock

假設(shè)我們要測試函數(shù) forEach 的內(nèi)部實現(xiàn),這個函數(shù)為傳入的數(shù)組中的每個元素調(diào)用一次回調(diào)函數(shù)。

function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}

為了測試此函數(shù),我們可以使用一個 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ù)是 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

// 第二次調(diào)用函數(shù)時的第一個參數(shù)是 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

// 第一次函數(shù)調(diào)用的返回值是 42
expect(mockCallback.mock.results[0].value).toBe(42);

mock的返回值

Mock 函數(shù)也可以用于在測試期間將測試值注入代碼︰

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

模擬接口返回

假定有個從 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)在,為測試該方法而不實際調(diào)用 API (使測試緩慢與脆弱),我們可以用 jest.mock(...) 函數(shù)自動模擬 axios 模塊。一旦模擬模塊,我們可為 .get 提供一個 mockResolvedValue ,它會返回假數(shù)據(jù)用于測試。

// 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ù)的匹配器

有了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();

也可以自己通過原生的匹配器模擬,下方的代碼與上方的等價:
// 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');

五、Vue Test Utils

官網(wǎng)是這樣介紹 Vue Test Utils 的:

Vue Test Utils 是 Vue.js 官方的單元測試實用工具庫。

以下的例子均基于 vue-cli 腳手架,包括 webpack/babel/vue-loader

測試單文件組件

Vue 的單文件組件在它們運行于 Node 或瀏覽器之前是需要預(yù)編譯的。我們推薦兩種方式完成編譯:通過一個 Jest 預(yù)編譯器,或直接使用 webpack。這里我們選用 Jest 的方式。

yarn add -D jest @vue/test-utils vue-jest

vue-jest 目前并不支持 vue-loader 所有的功能,比如自定義塊和樣式加載。額外的,諸如代碼分隔等 webpack 特有的功能也是不支持的。如果要使用這些不支持的特性,你需要用 Mocha 取代 Jest 來運行你的測試,同時用 webpack 來編譯你的組件。

處理 webpack 別名

vue-cli 中默認(rèn)使用 @ 作為 /src 的別名,在 Jest 也需要單獨配置:

// jest.config.js

module.exports = {
    moduleNameMapper: {
        '^@/(.*)$': '<rootDir>/src/$1'
    }
}

掛載組件

被掛載的組件會返回到一個包裹器內(nèi),而包裹器會暴露很多封裝、遍歷和查詢其內(nèi)部的 Vue 組件實例的便捷的方法。

// test.js

// 從測試實用工具集中導(dǎo)入 `mount()` 方法
// 同時導(dǎo)入你要測試的組件
import { mount } from '@vue/test-utils'
import Counter from './counter'

// 現(xiàn)在掛載組件,你便得到了這個包裹器
const wrapper = mount(Counter)

// 你可以通過 `wrapper.vm` 訪問實際的 Vue 實例
const vm = wrapper.vm

// 在控制臺將其記錄下來即可深度審閱包裹器
// 我們對 Vue Test Utils 的探索也由此開始
console.log(wrapper)

在掛載的同時,可以設(shè)置組件的各種屬性:

const wrapper = mount(Counter, {
    localVue,
    data() {
        return {
            bar: 'my-override'
        }
    },
    propsData: {
        msg: 'abc'
    },
    parentComponent: Foo, // 指定父組件
    provide: {
        foo() {
            return 'fooValue'
        }
    }
})

測試組件渲染出來的 HTML

通過包裹器wrapper的相關(guān)方法,判斷組件渲染出來的HTML是否符合預(yù)期。

import { mount } from '@vue/test-utils'
import Counter from './counter'

describe('Counter', () => {
  // 現(xiàn)在掛載組件,你便得到了這個包裹器
  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)用戶點擊按鈕的時候,我們的計數(shù)器應(yīng)該遞增。為了模擬這一行為,我們首先需要通過 wrapper.find() 定位該按鈕,此方法返回一個該按鈕元素的包裹器。然后我們能夠通過對該按鈕包裹器調(diào)用 .trigger() 來模擬點擊。

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)
})

為了測試計數(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')
})

組件的事件

每個掛載的包裹器都會通過其背后的 Vue 實例自動記錄所有被觸發(fā)的事件。你可以用 wrapper.emitted() 方法取回這些事件記錄。

wrapper.vm.$emit('foo')
wrapper.vm.$emit('foo', 123)

/*
`wrapper.emitted()` 返回以下對象:
{
  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!')
  })
})

組件的data

可以使用 setData() 或 setProps 設(shè)置組件的狀態(tài)數(shù)據(jù):

it('manipulates state', async () => {
  await wrapper.setData({ count: 10 })

  await wrapper.setProps({ foo: 'bar' })
})

模擬vue實例方法

由于Vue Test Utils 的 setMethods() 即將廢棄,推薦使用 jest.spyOn() 方法來模擬Vue實例方法:

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'

// 以下全局注冊的插件在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 中安裝全局插件時,可以使用 localVue,這會創(chuàng)建一個臨時的Vue實例:

import { createLocalVue, mount } from '@vue/test-utils'

// 創(chuàng)建一個擴展的 `Vue` 構(gòu)造函數(shù)
const localVue = createLocalVue()

// 正常安裝插件
localVue.use(MyPlugin)

// 在掛載選項中傳入 `localVue`
mount(Component, {
  localVue
})

測試watch

假如我們有一個這樣的watcher:

watch: {
  inputValue(newVal, oldVal) {
    if (newVal.trim().length && newVal !== oldVal) {
      console.log(newVal)
    }
  }
}

由于watch的調(diào)用是異步的,并且在下一個tick才會調(diào)用,因此可以通過檢測watcher里的方法是否被調(diào)用來檢測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)我們使用一些第三方插件的時候,一般不需要關(guān)心其內(nèi)部的實現(xiàn),不需要測試其組件,可以使用 shallowMount 代替 mount, 減少不必要的渲染:

import { shallowMount } from '@vue/test-utils'

const wrapper = shallowMount(Component)
wrapper.vm // 掛載的 Vue 實例

還可以通過 findAllComponents 來查找第三方組件:
import { Select } from 'element-ui'
test('選中總部時不顯示分部和網(wǎng)點', async () => {
    await wrapper.setProps({
        value: {
            clusterType: 'head-quarter-sit',
            branch: '',
            site: ''
        }
    })
    // 總部不顯示分部和網(wǎng)點
    expect(wrapper.findAllComponents(Select)).toHaveLength(1)
})

六、總結(jié)

單元測試?yán)碚?/p>

  • 單元測試能夠持續(xù)驗證代碼的正確性、驅(qū)動開發(fā),并起到一定的文檔作用;
  • 測試時數(shù)據(jù)盡量模擬現(xiàn)實,只考慮測試,不考慮內(nèi)部代碼;
  • 測試時充分考慮數(shù)據(jù)的邊界條件
  • 對重點、復(fù)雜、核心代碼,重點測試
  • 編寫單元測試有以下階段:準(zhǔn)備階段、執(zhí)行階段、斷言階段、清理階段;
  • 單元測試的工具可分為三類:測試運行器(Test Runner)、測試框架、工具庫。

Jest

  • --watch 選項可以監(jiān)聽文件的編碼,自動執(zhí)行單元測試;
  • 測試異步代碼可以用 done 方法或 aync 函數(shù);
  • mock函數(shù)可以捕獲這個函數(shù)的調(diào)用、this、返回值等,測試回調(diào)函數(shù)時非常有用。

Vue Test Utils

  • 用 mount 方法掛載組件,并可自定義各種vue屬性;
  • shallowMount 方法不渲染子組件,從而加快測試速度;
  • setupFiles 可以設(shè)置全局環(huán)境,如安裝 element-ui;
  • createLocalVue 可在創(chuàng)建單獨的vue實例,與全局的隔離;

到此這篇關(guān)于前端Vue單元測試入門教程的文章就介紹到這了,更多相關(guān)Vue單元測試內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue3+ts+vite2項目實戰(zhàn)踩坑記錄

    vue3+ts+vite2項目實戰(zhàn)踩坑記錄

    最近嘗試上手Vue3+TS+Vite,對比起Vue2有些不適應(yīng),但還是真香,下面這篇文章主要給大家介紹了關(guān)于vue3+ts+vite2項目的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-09-09
  • buildAdmin開源項目引入四種圖標(biāo)方式詳解

    buildAdmin開源項目引入四種圖標(biāo)方式詳解

    這篇文章主要為大家介紹了buildAdmin開源項目引入四種圖標(biāo)方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • Vue3使用setup如何定義組件的name屬性詳解

    Vue3使用setup如何定義組件的name屬性詳解

    vue3中新增了setup,它的出現(xiàn)是為了解決組件內(nèi)容龐大后,理解和維護(hù)組件變得困難的問題,下面這篇文章主要給大家介紹了關(guān)于Vue3使用setup如何定義組件的name屬性的相關(guān)資料,需要的朋友可以參考下
    2022-06-06
  • element-ui table行點擊獲取行索引(index)并利用索引更換行順序

    element-ui table行點擊獲取行索引(index)并利用索引更換行順序

    這篇文章主要介紹了element-ui table行點擊獲取行索引(index)并利用索引更換行順序,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • VUE UPLOAD 通過ACTION返回上傳結(jié)果操作

    VUE UPLOAD 通過ACTION返回上傳結(jié)果操作

    這篇文章主要介紹了VUE UPLOAD 通過ACTION返回上傳結(jié)果操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • vue 中filter的多種用法

    vue 中filter的多種用法

    這篇文章主要介紹了vue 中filter的多種用法,本文通過實例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2018-04-04
  • vue中的事件觸發(fā)(emit)及監(jiān)聽(on)問題

    vue中的事件觸發(fā)(emit)及監(jiān)聽(on)問題

    這篇文章主要介紹了vue中的事件觸發(fā)(emit)及監(jiān)聽(on)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • 分分鐘玩轉(zhuǎn)Vue.js組件(二)

    分分鐘玩轉(zhuǎn)Vue.js組件(二)

    這篇文章教大家如何分分鐘玩轉(zhuǎn)Vue.js組件,完善了vue.js組件的學(xué)習(xí)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • 使用vue3+TS實現(xiàn)簡易組件庫的全過程

    使用vue3+TS實現(xiàn)簡易組件庫的全過程

    當(dāng)市面上主流的組件庫不能滿足我們業(yè)務(wù)需求的時候,那么我們就有必要開發(fā)一套屬于自己團(tuán)隊的組件庫,下面這篇文章主要給大家介紹了如何使用vue3+TS實現(xiàn)簡易組件庫的相關(guān)資料,需要的朋友可以參考下
    2022-03-03
  • 詳解Vue的鉤子函數(shù)(路由導(dǎo)航守衛(wèi)、keep-alive、生命周期鉤子)

    詳解Vue的鉤子函數(shù)(路由導(dǎo)航守衛(wèi)、keep-alive、生命周期鉤子)

    這篇文章主要介紹了詳解Vue的鉤子函數(shù)(路由導(dǎo)航守衛(wèi)、keep-alive、生命周期鉤子),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-07-07

最新評論