Vue Unit Test:Action

栏目: 编程语言 · 发布时间: 5年前

内容简介:Vuex 分成 State、Mutation、Getter 與 Action 四部分,由於都是圍繞在 Data,因此都是針對 Data 做測試。本文討論 Action 的 Unit Test。Vue 2.5.22

Vuex 分成 State、Mutation、Getter 與 Action 四部分,由於都是圍繞在 Data,因此都是針對 Data 做測試。

本文討論 Action 的 Unit Test。

Version

Vue 2.5.22

Vuex 3.0.1

Vue-test-utils 1.0.0-beta.20

Store

books-info.js

import { fetchBooks } from '@/api/books';

/** mutation */
const setBooks = (state, { books }) => state.books = books;

/** action */
const saveBooks = commit => ({ data }) => commit('setBooks', data);
const loadBooks = ({ commit }) => fetchBooks().then(saveBooks(commit));

/** getter */
const booksCount = ({ books }) => books.length;

export default {
  namespaced: true,
  state: { books: [] },
  mutations: { setBooks },
  getters: { booksCount },
  actions: { loadBooks },
};

一個典型的 Vuex store,包含 statemutationsgettersactions

第 6 行

/** action */
const saveBooks = commit => ({ data }) => commit('setBooks', data);
const loadBooks = ({ commit }) => fetchBooks().then(saveBooks(commit));

loadBooks() 為典型的 action,呼叫 fetchBooks() API function,並將結果呼叫 setBooks() mutation 寫入 books state。

Unit Test

Promise

action.spec.js

import Axios from 'axios';
import store from '@/store/modules/books-info';

jest.mock('axios');

test('loadBooks() action', () => {
  /** arrange */
  const stub = [1, 2, 3];
  Axios.get.mockImplementation(() => Promise.resolve({ 
      data: { books: stub } 
  }));

  /** act */
  const commit = jest.fn();
  store.actions.loadBooks({ commit }).then(() => {
    /** assert */
    expect(commit).toHaveBeenCalledWith('setBooks', { books: stub } )
  });
});

第 1 行

import store from '@/store/modules/books-info';

將要測試的 store import 進來。

第 4 行

jest.mock('axios');

使用 jest.mock()axios 加以 mock,因為 axios 負責 API 所傳回的非同步資料。

第 6 行

test('loadBooks() action', () => {
  /** arrange */

  /** act */

  /** assert */
});

所有的 unit test 都包在 test() 的第二個參數,以 Arrow Function 表示。

test() 的第一個參數為 description,可描述 test case。

一樣使用 3A 原則寫 unit test。

第 7 行

/** arrange */
const stub = [1, 2, 3];
Axios.get.mockImplementation(() => Promise.resolve({ 
  data: { books: stub } 
}));

要測試 action,有三個原則:

  1. 提供 stub 並透過 Jest 的 mockImplementation()Axios.get() 做 mock
  2. 將 stub 透過 Promise 傳回
  3. 使用 Jest 的 toHaveBeenCalledWith() 驗證 mutation 是否被執行過
const stub = [1, 2, 3];

建立要測試的 stub。

Axios.get.mockImplementation(() => Promise.resolve({ 
  data: { books: stub } 
}));

使用 Jest 的 mockImplementation()Axios.get() 進行 mock。

由於 Axios.get() 為 function,因此要傳入一個 mock function 給 mockImplementation()

因為 Axios.get() 回傳為 Promise,因此要使用 Promise.resolve() 將 stub 封裝成 Promise 回傳。

13 行

/** act */
const commit = jest.fn();
store.actions.loadBooks({ commit }).then(() => {
  /** assert */
  expect(commit).toHaveBeenCalledWith('setBooks', { books: stub } )
});

由於 action 會呼叫 mutation 寫入 state,因此特別使用 jest.fn() 建立 fake commit object。

實際執行 loadBooks() action,並傳入剛剛剛建立的 fake commit。

因為 loadBooks() action 會呼叫 API function,屬於非同步行為,因此必須將 assertion 寫在 Promise then() 的 callback 內。

/** assert */
expect(commit).toHaveBeenCalledWith('setBooks', { books: stub } )

由於 loadBooks() action 會呼叫 setBooks() mutation,因此可以 setBooks() 是否被呼叫過作為測試手段,且一併驗證 payload 是否如預期。

Async Await

action.spec.js

import Axios from 'axios';
import store from '@/store/modules/books-info';

jest.mock('axios');

test('loadBooks() action', async () => {
  /** arrange */
  const stub = [1, 2, 3];
  Axios.get.mockResolvedValue({ 
    data: { books: stub } 
  });

  /** act */
  const commit = jest.fn();
  await store.actions.loadBooks({ commit });

  /** assert */
  expect(commit).toHaveBeenCalledWith('setBooks', { books: stub } );
});

第 6 行

test('loadBooks() action', async () => {
  /** arrange */

  /** act */

  /** assert */
});

所有的 unit test 都包在 test() 的第二個參數,以 Arrow Function 表示,與 Promise 寫法不同的是:在 () 之前加了 async ,因為 action 會呼叫 API function,而 API function 回傳為 Promise,為了要使用 async await 語法,只好將 test function 升級為 async function。

test() 的第一個參數為 description,可描述 test case。

一樣使用 3A 原則寫 unit test。

Production code 較不建議使用 ES2017 的 async await ,主要是 async await 是 Imperative 風格的 syntatic sugar,語義不如 Promise then() 的 Pipeline 清楚,還會誤以為是在寫同步程式碼,但 Unit Test 則是例外,因為 Unit Test 目前基本上是以 Imperative 方式實踐,而不是 Functional 風格,因此 async await 較適合 testing code

第 7 行

Axios.get.mockResolvedValue({ 
  data: { books: stub } 
});

除了 mockImplementation() 外,也可以使用 mockResolvedValue() ,它回傳就是 Promise,因此不必再使用 Promise.resolve() 封裝 stub。

13 行

/** act */
const commit = jest.fn();
await store.actions.loadBooks({ commit });

由於 action 會呼叫 mutation 寫入 state,因此特別使用 jest.fn() 建立 fake commit object。

實際執行 loadBooks() action,並傳入剛剛剛建立的 fake commit。

因為 loadBooks() action 會呼叫 API function,屬於非同步行為,因此特別加上了 await

17 行

/** assert */
expect(commit).toHaveBeenCalledWith('setBooks', { books: stub } );

由於 loadBooks() action 會呼叫 setBooks() mutation,因此可以 setBooks() 是否被呼叫過作為測試手段,且一併驗證 payload 是否如預期。

$ yarn test:unit

Vue Unit Test:Action

Conclusion

  • 若以結果論, mockImplementation()mockResolvedValue() 的目的相同,都是想對 API function 結果做 mock,但 mockImplementation() 是傳入 function,語意較佳;但 mockResolvedValue() 較為精簡,且回傳已是 Promise
  • 使用 Promise 或 Async Await 都可以測試 action,Promise 的語意較佳,可凸顯非同步,但 assertion 必須寫在 then() 的 callback;但 Async Await 語法較美,3A 原則都在同一層
  • Unit test 基本上是 Imperative 風格實踐,因此實務上建議使用 mockResolvedValue() + Async Await 對 action 做 unit test

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

疯狂又脆弱  坚定又柔软

疯狂又脆弱 坚定又柔软

朱墨 / 湖南文艺出版社 / 2018-3 / 39.80元

《疯狂又脆弱 坚定又柔软》是朱墨的一部作品集,介绍了作者考研到北京,工作在华谊,以及留学去英国的经历,在这短短几年中她一路升职加薪,25岁升任华谊宣传总监,27岁赚到人生的第一笔100万,30岁却毅然离职去英国留学,在表面的光鲜亮丽之下,她也曾付出过外人所不知道的心血和努力。她的人生告诉我们,每一个身居高位或者肆意潇洒的人,都曾为梦想疯狂地倾尽全力,而那些心怀梦想的人也总是怀揣一颗坚定又柔软的内心......一起来看看 《疯狂又脆弱 坚定又柔软》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具