Vue,vuex:如何使用命名空间存储和模拟对组件进行单元测试?
Vue, vuex: how to unit test a component with namespaced store and mocking?
我正在使用 vue、vuex 和 vuetify 构建一个登录组件。我决定在商店中使用带命名空间的身份验证模块,这导致了我的问题。
我正在使用 TDD 来解决这个问题。我的 e2e 测试有效。但是我编写了这个单元测试(使用 mockStore),它应该只验证是否已发送正确的操作:
describe('Login component', () => {
let wrapper
const mockStore = {
dispatch: jest.fn(),
}
beforeEach(() => {
wrapper = mount(Login, {
localVue,
mocks: { $store: mockStore },
computed: {
error: () => 'test error',
},
data: () => ({
valid: true
})
})
})
it('should dispatch login action', async () => {
wrapper.find('[data-test="username"]').setValue('username')
wrapper.find('[data-test="password"]').setValue('password')
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
wrapper.find('[data-test="login"]').trigger('click')
await wrapper.vm.$nextTick()
expect(mockStore.dispatch).toHaveBeenCalledWith(
`auth/${LOGIN}`,
{ username: 'username', password: 'password' }
)
})
})
该组件仅按以下方式使用 mapActions
:
...mapActions('auth', [LOGIN])
以及触发它的按钮:
<v-btn
color="info"
@click="login({ username, password })"
data-test="login"
:disabled="!valid"
>Login</v-btn>
我得到的错误是:
[Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'auth/' of undefined"
如果我在 mapActions
中删除命名空间,那么我得到的分派操作名称没有命名空间(duh)并且测试失败:
- Expected
+ Received
- "auth/login",
+ "login",
我实际上能够通过像这样的映射操作来修复它:
...mapActions({ login: `auth/${LOGIN}` })
但我真的更喜欢使用命名空间版本,因为当我有更多操作时它会变得丑陋。
我正在研究 vuex 源代码,但在尝试访问 _modulesNamespaceMap
时失败了,但后来对我来说太复杂了。
测试这个的最佳方法是什么?此时我应该放弃模拟并使用真实商店吗?
完整项目可用 here 与此问题相关的提交是 4a7e749d4
建立在 the example on the vue-test-utils
docs 基础上,我认为这应该可行:
/* ... other imports and setup ... */
import Vuex from 'vuex'
describe('Login component', () => {
let wrapper
const actions = {
login: jest.fn(),
}
const mockStore = new Vuex({
modules: {
auth: {
namespaced: true,
actions,
},
},
})
beforeEach(() => {
wrapper = mount(Login, {
localVue,
mocks: { $store: mockStore },
computed: {
error: () => 'test error',
},
data: () => ({
valid: true
})
})
})
it('should dispatch login action', async () => {
wrapper.find('[data-test="username"]').setValue('username')
wrapper.find('[data-test="password"]').setValue('password')
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
wrapper.find('[data-test="login"]').trigger('click')
await wrapper.vm.$nextTick()
expect(actions.login).toHaveBeenCalled() // <-- pretty sure this will work
expect(actions.login).toHaveBeenCalledWith({ // <-- not as sure about this one
username: 'username',
password: 'password',
})
})
})
对于转向 Vue 3 Test Utils 的任何人,请注意上述答案所依赖的 createLocalVue
方法已在 @vue/test-utils 中删除(请参阅 https://next.vue-test-utils.vuejs.org/migration/#no-more-createlocalvue)。
相反,它建议使用 Vuex 的 createStore
方法。我能够让命名空间模块像这样工作:
/* ... other imports and setup ... */
import { mount } from "@vue/test-utils";
import Logon from "path/to/your/logon/component";
import { createStore } from "vuex";
describe('Login component', () => {
const actions = {
login: jest.fn(),
};
const mockStore = createStore({
modules: {
auth: {
namespaced: true,
actions,
},
},
});
let wrapper;
beforeEach(() => {
wrapper = mount(Login, {
global: {
plugins: [mockStore],
},
});
});
it('should dispatch login action', async () => {
/*...test code goes here */
})
})
我正在使用 vue、vuex 和 vuetify 构建一个登录组件。我决定在商店中使用带命名空间的身份验证模块,这导致了我的问题。
我正在使用 TDD 来解决这个问题。我的 e2e 测试有效。但是我编写了这个单元测试(使用 mockStore),它应该只验证是否已发送正确的操作:
describe('Login component', () => {
let wrapper
const mockStore = {
dispatch: jest.fn(),
}
beforeEach(() => {
wrapper = mount(Login, {
localVue,
mocks: { $store: mockStore },
computed: {
error: () => 'test error',
},
data: () => ({
valid: true
})
})
})
it('should dispatch login action', async () => {
wrapper.find('[data-test="username"]').setValue('username')
wrapper.find('[data-test="password"]').setValue('password')
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
wrapper.find('[data-test="login"]').trigger('click')
await wrapper.vm.$nextTick()
expect(mockStore.dispatch).toHaveBeenCalledWith(
`auth/${LOGIN}`,
{ username: 'username', password: 'password' }
)
})
})
该组件仅按以下方式使用 mapActions
:
...mapActions('auth', [LOGIN])
以及触发它的按钮:
<v-btn
color="info"
@click="login({ username, password })"
data-test="login"
:disabled="!valid"
>Login</v-btn>
我得到的错误是:
[Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'auth/' of undefined"
如果我在 mapActions
中删除命名空间,那么我得到的分派操作名称没有命名空间(duh)并且测试失败:
- Expected
+ Received
- "auth/login",
+ "login",
我实际上能够通过像这样的映射操作来修复它:
...mapActions({ login: `auth/${LOGIN}` })
但我真的更喜欢使用命名空间版本,因为当我有更多操作时它会变得丑陋。
我正在研究 vuex 源代码,但在尝试访问 _modulesNamespaceMap
时失败了,但后来对我来说太复杂了。
测试这个的最佳方法是什么?此时我应该放弃模拟并使用真实商店吗?
完整项目可用 here 与此问题相关的提交是 4a7e749d4
建立在 the example on the vue-test-utils
docs 基础上,我认为这应该可行:
/* ... other imports and setup ... */
import Vuex from 'vuex'
describe('Login component', () => {
let wrapper
const actions = {
login: jest.fn(),
}
const mockStore = new Vuex({
modules: {
auth: {
namespaced: true,
actions,
},
},
})
beforeEach(() => {
wrapper = mount(Login, {
localVue,
mocks: { $store: mockStore },
computed: {
error: () => 'test error',
},
data: () => ({
valid: true
})
})
})
it('should dispatch login action', async () => {
wrapper.find('[data-test="username"]').setValue('username')
wrapper.find('[data-test="password"]').setValue('password')
await wrapper.vm.$nextTick()
await wrapper.vm.$nextTick()
wrapper.find('[data-test="login"]').trigger('click')
await wrapper.vm.$nextTick()
expect(actions.login).toHaveBeenCalled() // <-- pretty sure this will work
expect(actions.login).toHaveBeenCalledWith({ // <-- not as sure about this one
username: 'username',
password: 'password',
})
})
})
对于转向 Vue 3 Test Utils 的任何人,请注意上述答案所依赖的 createLocalVue
方法已在 @vue/test-utils 中删除(请参阅 https://next.vue-test-utils.vuejs.org/migration/#no-more-createlocalvue)。
相反,它建议使用 Vuex 的 createStore
方法。我能够让命名空间模块像这样工作:
/* ... other imports and setup ... */
import { mount } from "@vue/test-utils";
import Logon from "path/to/your/logon/component";
import { createStore } from "vuex";
describe('Login component', () => {
const actions = {
login: jest.fn(),
};
const mockStore = createStore({
modules: {
auth: {
namespaced: true,
actions,
},
},
});
let wrapper;
beforeEach(() => {
wrapper = mount(Login, {
global: {
plugins: [mockStore],
},
});
});
it('should dispatch login action', async () => {
/*...test code goes here */
})
})