vuex
怎麼單元測試,我們只能通過檢驗state的值是否符合預期來測試,所以,正常的套路應該是測試mutation,然後看看對應的state是否發生了符合預期的變化。沒錯。原文地址vue單元測試vuex,mutation,尤其是actions、getters怎麼測?讓你像使用vuex一樣測試vuex
mutation 怎麼測
比如這種
SET_LIST(state, payload) {
state.listData = payload;
},
複製程式碼
// test.spec.js
describe("mutations", async () => {
it("mutations SET_LIST", async function() {
SET_LIST(state, [123]);
expect(state.listData).eql([123]);
});
}
複製程式碼
每個mutation就是一個方法,我們直接呼叫 SET_LIST(state, payload)
檢驗對應的state就可以完成對SET_LIST
的測試了。這當然簡單了,畢竟mutation裡面就這兩個引數
action呢
action裡面一般伴有傳送請求,然後根據請求結果,觸發對應的mutation,有時我們還會在action裡面觸發另外的action等較複雜的情況 下面列舉了我們不願意測action的原因:
- action可能進行介面呼叫
- action可能傳送當前module或其他module的state或getters
- action可能傳送當前module或其他module的action
- 上述情況可能會組合出現
在測試這樣的action時是不是會吐槽,action怎麼會寫這麼複雜。哈哈,業務需要啊。
action裡面也是兩個引數,只是第一個引數context
是一個物件
state
、rootState
、getters
、rootGetters
這幾個物件用來獲取值以外,裡面還有commit
、dispatch
方法。
比如下面的兩個action就比較複雜,還有“巢狀”關係
async _getDetail({ rootState, dispatch }, params) {
const [res] = await Promise.all([
rootState.Axios.post(rootState.Api.pim.propGetAttr, params),
rootState.pim.root.langResult
]);
dispatch("handleDetailResponse", { res, params });
return res;
},
// 處理新建/編輯屬性的通用response
async handleDetailResponse({ commit, dispatch }, { res, params }) {
if (httpSuccess(res)) {
commit("SET_DETAIL", res.data.data.data);
const paginator = (res.data.data && res.data.data.paginator) || {};
if (paginator.totalCount > paginator.limit) {
dispatch("_getDetailAllValueVOList", {
...params,
page: {
page: 1,
size: paginator.totalCount
}
});
} else {
const { valueVOList } = res.data.data.data || {};
commit("SET_ATTR_VALUES", valueVOList);
commit("SET_ATTR_VALUE_PAGINATOR", paginator);
}
}
},
複製程式碼
問題一:這種我們怎麼測試_getDetail
這個action呢?跟mutation一樣?不行啊,第一個引數{commit, dispatch}
的commit
和dispatch
怎麼傳?
問題二:如果在store裡面使用this
怎麼辦?this.dispatch
或者this.state.pim.listData
這種寫法怎麼辦?
action裡面的this
是含有下列屬性的。
市面上的解決方案
測試action的童鞋自然是發現這個問題的,所以,大家一般都選擇了“曲線救國”--拆分action測試
- 只測試對應的action,只要傳送了就行
- action裡面的mutation模擬資料後測試
- 裡面巢狀其它action、mutaion一樣也進行拆分
上圖所示的Action1的測試會分成2個部分進行測試。
- Action1 -> Action2
- Mutation3
- Mutation1
- Mutation2
其實Action1 -> Action2
好不好測試,我這裡存疑。
好多人都是用sinon
來實現測試action,sinon
怎麼用我不知道,我只是通過程式碼來看,這種測試action的沒什麼用,就是為了提交單元測試覆蓋率而已。
我怎麼測?
直接複用store裡面的所有程式碼來實現全流程測試(介面資料肯定得模擬了)
這樣的話,擋在前面的問題就出現了,在store的方法裡面的commit
,dispatch
,this
怎麼辦?
我們不是學過使用bind
,call
嗎?難道這些只是為了面試的嗎,遇到問題解決問題啊。
store的每個module就是一個物件啊,裡面有屬性state
、mutations
、actions
、getters
而已。按照vuex的使用方式模擬一個基本跟原有commit
,dispatch
一樣功能的函式即可。
先看效果。
原有store如下,有多層子module:
// store.js
export default {
namespace: true,
state: {
abc: 1
},
mutations: {
SET(state, payload) {
state.abc = payload;
},
PLUS(state, payload) {
state.abc = state.abc + payload;
},
},
actions: {
_plus(context, params) {
console.log("context -> ", context);
context.commit("PLUS", params);
}
},
getters: {
getStatePlus(state, getters, rootState, rootGetters) {
console.log(
"getters getStatePlus-> ",
state,
rootState,
Object.getOwnPropertyNames(getters),
Object.getOwnPropertyNames(rootGetters)
);
return state.abc + 1;
}
},
modules: {
sub: {
namespace: true,
state: {
cbd: 100
},
mutations: {
SET(state, payload) {
state.cbd = payload;
},
MINUS(state, payload) {
console.log(" MINUS arg-> ", arguments);
state.cbd = state.cbd - payload;
console.log(" MINUS result-> ", state.cbd);
}
},
actions: {
_minus(context, params) {
console.log("context -> ", context);
context.commit("MINUS", params);
}
},
getters: {
getStateMinus(state, getters, rootState, rootGetters) {
console.log(
"getters getStateMinus-> ",
state,
rootState,
Object.getOwnPropertyNames(getters),
Object.getOwnPropertyNames(rootGetters)
);
return state.cbd - 1;
}
},
modules: {
subTie: {
namespace: true,
state: {
xyz: 55
},
mutations: {
SET(state, payload) {
state.xyz = payload;
},
MULTIPLY(state, payload) {
state.xyz = state.xyz * payload;
}
},
actions: {
_multiply(context, params) {
console.log("context -> ", context);
context.commit("MULTIPLY", params);
}
},
getters: {
getStateMultiplyDouble(state) {
console.log("getters getStateMultiplyDouble-> ", arguments);
return state.xyz * 2;
}
}
}
}
}
}
};
複製程式碼
在測試用例裡面使用也很簡單,跟我們在action裡面觸發mutation和action一致,是不是很爽。
// test.spec.js
import { expect } from "chai";
import VuexTester from 'vuex-tester';
import store from '../store';
const {commit, dispatch, rootState, state, getters, rootGetters} = new VuexTester(store).update();
describe("test state", async () => {
it("state abc", async function() {
expect(state.abc).eql(1);
});
});
describe("test rootState", async () => {
it("rootState abc", async function() {
expect(rootState.abc).eql(1);
});
});
describe("test mutations", async () => {
it("mutations SET", async function() {
commit('SET', 100);
expect(state.abc).eql(100);
});
it("mutations PLUS", async function() {
commit('SET', 100);
commit('PLUS', 10);
expect(state.abc).eql(110);
});
});
describe("test actions", async () => {
it("actions _plus", async function() {
commit('SET', 100);
dispatch('_plus', 9);
expect(state.abc).eql(109);
});
});
describe("test getters", async () => {
it("getters getStatePlus", async function() {
commit('SET', 100);
expect(getters.getStatePlus).eql(101);
});
});
describe("test rootGetters", async () => {
it("rootGetters getStatePlus", async function() {
commit('SET', 100);
expect(rootGetters.getStatePlus).eql(101);
});
});
複製程式碼
我放部分核心程式碼
update(storeContext = this.store, namespace = this.namespace) {
const fn = this.getFn(namespace);
const {
state = {},
actions = {},
mutations = {},
getters = {}
} = storeContext;
const boundCommit = (type, payload) => {
console.log("[commit ]: ", type, payload);
// mutation in vuex return noting, but we return state
return (
fn(type, this.rootMutationsMap, mutations).call(
this.storeContext,
state,
payload
) || state
);
};
const boundDispatch = (type, payload) => {
console.log("[dispatch]: ", type, payload);
return fn(type, this.rootActionsMap, actions).call(
this.storeContext,
this.context,
payload
);
};
this.storeContext.commit = boundCommit;
this.storeContext.dispatch = boundDispatch;
this.initGetter(namespace, getters, state);
// core state and function in vuex context
this.context = {
rootState: this.storeContext.state,
state: state,
commit: boundCommit,
dispatch: boundDispatch,
getters: this.gettersMap,
rootGetters: this.rootGettersMap
};
return this.context;
}
複製程式碼
哈哈,看到你心心念唸的commit
,dispatch
,state
,getters
,this
了吧
我把自己的想法發到了npm上面,地址vuex-tester。
具體程式碼在github上面。
建議clone下來,直接執行npm run test
進行測試。