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
进行测试。