使用Jest測試JavaScript(Mock篇)

在本篇教程中,咱們會介紹 Jest 中的三個與 Mock 函數相關的API,分別是jest.fn()jest.spyOn()jest.mock()。使用它們建立Mock函數可以幫助咱們更好的測試項目中一些邏輯較複雜的代碼,例如測試函數的嵌套調用,回調函數的調用等。ios

若是你還不知道 Jest的基本使用方法,請先閱讀: 《使用Jest測試JavaScript (入門篇)》

爲何要使用Mock函數?

在項目中,一個模塊的方法內經常會去調用另一個模塊的方法。在單元測試中,咱們可能並不須要關心內部調用的方法的執行過程和結果,只想知道它是否被正確調用便可,甚至會指定該函數的返回值。此時,使用Mock函數是十分有必要。shell

Mock函數提供的如下三種特性,在咱們寫測試代碼時十分有用:npm

  • 捕獲函數調用狀況
  • 設置函數返回值
  • 改變函數的內部實現
咱們接着使用 上篇文章中的目錄結構,在 test/functions.test.js文件中編寫測試代碼, src/目錄下寫被測試代碼。

1. jest.fn()

jest.fn()是建立Mock函數最簡單的方式,若是沒有定義函數內部的實現,jest.fn()會返回undefined做爲返回值。json

// functions.test.js

test('測試jest.fn()調用', () => {
  let mockFn = jest.fn();
  let result = mockFn(1, 2, 3);

  // 斷言mockFn的執行後返回undefined
  expect(result).toBeUndefined();
  // 斷言mockFn被調用
  expect(mockFn).toBeCalled();
  // 斷言mockFn被調用了一次
  expect(mockFn).toBeCalledTimes(1);
  // 斷言mockFn傳入的參數爲1, 2, 3
  expect(mockFn).toHaveBeenCalledWith(1, 2, 3);
})

jest.fn()所建立的Mock函數還能夠設置返回值定義內部實現返回Promise對象axios

// functions.test.js

test('測試jest.fn()返回固定值', () => {
  let mockFn = jest.fn().mockReturnValue('default');
  // 斷言mockFn執行後返回值爲default
  expect(mockFn()).toBe('default');
})

test('測試jest.fn()內部實現', () => {
  let mockFn = jest.fn((num1, num2) => {
    return num1 * num2;
  })
  // 斷言mockFn執行後返回100
  expect(mockFn(10, 10)).toBe(100);
})

test('測試jest.fn()返回Promise', async () => {
  let mockFn = jest.fn().mockResolvedValue('default');
  let result = await mockFn();
  // 斷言mockFn經過await關鍵字執行後返回值爲default
  expect(result).toBe('default');
  // 斷言mockFn調用後返回的是Promise對象
  expect(Object.prototype.toString.call(mockFn())).toBe("[object Promise]");
})

上面的代碼是jest.fn()提供的幾個經常使用的API和斷言語句,下面咱們在src/fetch.js文件中寫一些被測試代碼,以更加接近業務的方式來理解Mock函數的實際應用。async

被測試代碼中依賴了 axios這個經常使用的請求庫和 JSONPlaceholder這個上篇文章中提到免費的請求接口,請先在 shell中執行 npm install axios --save安裝依賴,。
// fetch.js

import axios from 'axios';

export default {
  async fetchPostsList(callback) {
    return axios.get('https://jsonplaceholder.typicode.com/posts').then(res => {
      return callback(res.data);
    })
  }
}

咱們在fetch.js中封裝了一個fetchPostsList方法,該方法請求了JSONPlaceholder提供的接口,並經過傳入的回調函數返回處理過的返回值。若是咱們想測試該接口可以被正常請求,只須要捕獲到傳入的回調函數可以被正常的調用便可。下面是functions.test.js中的測試的代碼。函數

import fetch from '../src/fetch.js'

test('fetchPostsList中的回調函數應該可以被調用', async () => {
  expect.assertions(1);
  let mockFn = jest.fn();
  await fetch.fetchPostsList(mockFn);

  // 斷言mockFn被調用
  expect(mockFn).toBeCalled();
})

2. jest.mock()

fetch.js文件夾中封裝的請求方法可能咱們在其餘模塊被調用的時候,並不須要進行實際的請求(請求方法已經經過單側或須要該方法返回非真實數據)。此時,使用jest.mock()去mock整個模塊是十分有必要的。post

下面咱們在src/fetch.js的同級目錄下建立一個src/events.js單元測試

// events.js

import fetch from './fetch';

export default {
  async getPostList() {
    return fetch.fetchPostsList(data => {
      console.log('fetchPostsList be called!');
      // do something
    });
  }
}

functions.test.js中的測試代碼以下:測試

// functions.test.js

import events from '../src/events';
import fetch from '../src/fetch';

jest.mock('../src/fetch.js');

test('mock 整個 fetch.js模塊', async () => {
  expect.assertions(2);
  await events.getPostList();
  expect(fetch.fetchPostsList).toHaveBeenCalled();
  expect(fetch.fetchPostsList).toHaveBeenCalledTimes(1);
});

在測試代碼中咱們使用了jest.mock('../src/fetch.js')去mock整個fetch.js模塊。若是註釋掉這行代碼,執行測試腳本時會出現如下報錯信息

從這個報錯中,咱們能夠總結出一個重要的結論:

在jest中若是想捕獲函數的調用狀況,則該函數必須被mock或者spy!

3. jest.spyOn()

jest.spyOn()方法一樣建立一個mock函數,可是該mock函數不只可以捕獲函數的調用狀況,還能夠正常的執行被spy的函數。實際上,jest.spyOn()jest.fn()的語法糖,它建立了一個和被spy的函數具備相同內部代碼的mock函數。

上圖是以前jest.mock()的示例代碼中的正確執行結果的截圖,從shell腳本中能夠看到console.log('fetchPostsList be called!');這行代碼並無在shell中被打印,這是由於經過jest.mock()後,模塊內的方法是不會被jest所實際執行的。這時咱們就須要使用jest.spyOn()

// functions.test.js

import events from '../src/events';
import fetch from '../src/fetch';

test('使用jest.spyOn()監控fetch.fetchPostsList被正常調用', async() => {
  expect.assertions(2);
  const spyFn = jest.spyOn(fetch, 'fetchPostsList');
  await events.getPostList();
  expect(spyFn).toHaveBeenCalled();
  expect(spyFn).toHaveBeenCalledTimes(1);
})

執行npm run test後,能夠看到shell中的打印信息,說明經過jest.spyOn()fetchPostsList被正常的執行了。

4. 總結

這篇文章中咱們介紹了jest.fn(),jest.mock()jest.spyOn()來建立mock函數,經過mock函數咱們能夠經過如下三個特性去更好的編寫咱們的測試代碼:

  • 捕獲函數調用狀況
  • 設置函數返回值
  • 改變函數的內部實現

在實際項目的單元測試中,jest.fn()常被用來進行某些有回調函數的測試;jest.mock()能夠mock整個模塊中的方法,當某個模塊已經被單元測試100%覆蓋時,使用jest.mock()去mock該模塊,節約測試時間和測試的冗餘度是十分必要;當須要測試某些必須被完整執行的方法時,經常須要使用jest.spyOn()。這些都須要開發者根據實際的業務代碼靈活選擇。

相關文章
相關標籤/搜索