Reputation: 638
I'm building a login component using vue, vuex and vuetify. I've decided to use a namespaced auth module in store and this is causing me the problem.
I'm approaching this using TDD. My e2e test works. But I wrote this unit test (using a mockStore) that should only verify that a proper action has been dispatched:
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' }
)
})
})
The component is only using mapActions
in the following way:
...mapActions('auth', [LOGIN])
And the button triggering it:
<v-btn
color="info"
@click="login({ username, password })"
data-test="login"
:disabled="!valid"
>Login</v-btn>
The error I'm getting is:
[Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'auth/' of undefined"
If I drop the namespace in mapActions
, then the dispatched action name I'm getting is not namespaced (duh) and the test fails:
- Expected
+ Received
- "auth/login",
+ "login",
I was actually able to fix it by mapping actions like so:
...mapActions({ login: `auth/${LOGIN}` })
But I would really prefer to use namespaced version, because it's gonna get ugly when I have more actions.
I was looking into vuex source code and it fails when trying to access _modulesNamespaceMap
but then it gets too complicated for me.
What's the best approach to test this? Should I just give up on mocking and use a real store at this point?
Full project available here and commit relevant to this question is 4a7e749d4
Upvotes: 9
Views: 4774
Reputation: 420
For anybody moving to Vue 3 Test Utils, please note that the createLocalVue
method that the above answer relies upon is removed in @vue/test-utils (see https://next.vue-test-utils.vuejs.org/migration/#no-more-createlocalvue).
Instead, it recommends using the createStore
method from Vuex. I was able to get namespaced modules working like this:
/* ... 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 */
})
})
Upvotes: 2
Reputation: 7945
Building off of the example on the vue-test-utils
docs, I think this should work:
/* ... 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',
})
})
})
Upvotes: 8