Vue Test Mock Promise-Based Action within Module - vue.js

I unfortunately can't attach all code or create a gist because the project I'm working on is related to work but I can give enough detail that I think it will work.
I'm trying to mock a call to an action that is stored in a different module but for the life of me I can't figure out how to. I'm able to create a Jest spy on the store.dispatch method but I want to be able to resolve the promise and make sure that the subsequent steps are taken.
The method in the SFC is
doSomething(data) {
this.$store.dispatch('moduleA/moduleDoSomething',{data: data})
.then(() => {
this.$router.push({name: 'RouteName'})
})
.catch(err => {
console.log(err)
alert('There was an error. Please try again.')
})
},
This is what my test looks like:
import { mount, createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import Vuetify from 'vuetify'
import Component from '#/components/Component'
import moduleA from '#/store/modules/moduleA'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Vuetify)
describe('Component.vue', () => {
let actions
let store
const $router = []
beforeEach(() => {
actions = {
moduleDoSomething: jest.fn((payload) => {
return Promise.resolve({
status: 200,
})
})
}
store = new Vuex.Store({
state: {},
modules: {
moduleA: {
actions
}
},
})
})
it('does something', () => {
const wrapper = mount(Component, {
store,
localVue,
mocks: {
$router,
},
})
let button = wrapper.find('button that calls doSomething')
button.trigger('click')
expect(actions.moduleDoSomething).toHaveBeenCalled()
expect(wrapper.vm.$router[0].name).toBe('RouteName')
})
})
The following test passes, but I don't want to just test that the action was dispatched; I also want to test things in the "then" block.
it('does something', () => {
const dispatchSpy = jest.spyOn(store, 'dispatch')
const wrapper = mount(Component, {
store,
localVue,
mocks: {
$router,
},
})
let button = wrapper.find('button that calls doSomething')
button.trigger('click')
expect(dispatchSpy).toHaveBeenCalledWith('moduleA/moduleDoSomething',{data: data})
})
})

I managed to solve this problem by simply making the module namespaced in the mocked store.
store = new Vuex.Store({
state: {},
modules: {
moduleA: {
actions,
namespaced: true
}
},
})
I'll delete the question in a little bit

Related

Is there a way to make my vue jest test with mock vuex instead of the app store

const localVue = createLocalVue();
localVue.use(Vuex);
describe('Dashboard component', () => {
let store;
let userDataStore;
beforeEach(() => {
userDataStore = {
namespaced: true,
state: {
sessionId: 'k5gv7lc3jvol82o91tddjtoi35kv16c3',
},
};
store = new Vuex.Store({
modules: {
userDataStore: userDataStore,
},
});
});
it('it renders the header component if there is a session id', () => {
const wrapper = shallowMount(DashboardPage, {
store,
localVue,
});
const headerComponent = wrapper.findComponent({ name: 'DashboardPage' });
expect(headerComponent.exists()).toBe(true);
});
});
but it keeps trying to access the app's main vuex and it should instead make use of the test mockup.

Mock of store action of vuex does not mocked

I have small vue component that on created hook dispatch some action
#Component
export default class SomeComponent extends Vue {
created() {
store.dispatch('module/myAction', { root: true });
}
}
and I wrote next test
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(VueRouter);
const localRouter = new VueRouter();
describe('SomeComponent.vue Test', () => {
let store: any;
beforeEach(() => {
store = new Vuex.Store({
modules: {
module: {
namespaced: true,
actions: {
myAction: jest.fn()
}
}
}
});
});
it('is component created', () => {
const wrapper = shallowMount(SomeComponent, {
localVue,
store,
propsData: {}
});
expect(wrapper.isVueInstance()).toBeTruthy();
});
});
but for some reason the "real" code are executed and I got a warning
isVueInstance() is deprecated. In your test you should mock $store object and it's dispatch function. I fixed typo in created(), here's my version of SomeComponent and working test, hope that would help.
#Component
export default class SomeComponent extends Vue {
created () {
this.$store.dispatch('module/myAction', { root: true })
}
}
import { shallowMount, Wrapper } from '#vue/test-utils'
import SomeComponent from '#/components/SomeComponent/SomeComponent.vue'
let wrapper: Wrapper<SomeComponent & { [key: string]: any }>
describe('SomeComponent.vue Test', () => {
beforeEach(() => {
wrapper = shallowMount(SomeComponent, {
mocks: {
$store: {
dispatch: jest.fn()
}
}
})
})
it('is component created', () => {
expect(wrapper.vm.$store.dispatch).toBeCalled()
expect(wrapper.vm.$store.dispatch).toBeCalledWith('module/myAction', { root: true })
})
})
Also keep in mind that when you test SomeComponent or any other component you should not test store functionality, you should just test, that certain actions/mutations were called with certain arguments. The store should be tested separately. Therefore you don't need to create real Vuex Store when you test components, you just need to mock $store object.

$router.push not triggering in unit testing

I have the following problem while trying to unit test my Vue application.
Even spying and mocking $router.push, I still can't make it to be called while inside unit testing:
This is my unit testing (Home.spec.js)
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(VueRouter);
describe('Home.vue', () => {
let actions;
let getters;
let store;
let router;
beforeEach(() => {
actions = {
[FETCH_USER_REPOSITORIES]: jest.fn()
};
getters = {
getTopThreeRepositories: jest.fn(repositoryMock.getRepositories)
};
store = new Vuex.Store({ getters, actions });
router = new VueRouter();
});
it('should redirect when 404 status code received', async () => {
jest.spyOn(store, 'dispatch').mockRejectedValue({ statusCode: 404 });
jest.spyOn(router, 'push').mockResolvedValue({});
const wrapper = await shallowMount(Home, {
store,
localVue,
router
});
expect(router.push).toHaveBeenCalledWith('/not-found');
});
});
Now, this is my Home.vue:
import { mapGetters } from 'vuex';
import { FETCH_USER_REPOSITORIES } from "../store/actions";
import RepositoryList from "#/components/RepositoryList";
import Card from "#/components/Card";
export default {
name: 'view-home',
components: {
Card,
RepositoryList
},
async beforeMount() {
try {
await this.$store.dispatch(FETCH_USER_REPOSITORIES, 'some-repo');
} catch(err) {
console.log(this.$router);
await this.$router.push('/not-found');
}
},
computed: {
...mapGetters(["getTopThreeRepositories"])
}
}
The console log shows the $router correctly, with the spy.
If I force the calling inside the unit testing, it works, but the expects always fails giving me back that $router.push hasn't been called.
Can anyone help me, please?
Thanks!
You should specify $store and $route as mocks in your mounting options, as shown below. Also there's no need to await shallowMount because shallowMount does not return a Promise, so the await would just return immediately.
describe('Home.vue', () => {
it('should redirect when 404 status code received', () => {
const $store = {
dispatch: jest.fn()
}
const $router = {
push: jest.fn()
}
const wrapper = shallowMount(Home, {
localVue,
mocks: {
$store,
$router,
}
});
expect($router.push).toHaveBeenCalledWith('/not-found');
})
})

How to test namespaced mapAction in Vue

I have a namespaced Vuex store in my Vue 2.6 app one module of which is like this:
//books.js
export default {
state: {
books: null
},
mutations: {
SET_BOOKS(state, books) {
state.books = books;
},
},
actions: {
setBooks: async function ({ commit }) {
//api call
commit('SET_BOOKS', books);
}
},
namespaced: true
};
I want to test a component that calls the setBooks action. I am using mapActions to access the actions. The relevant code is:
//Books.vue
methods: {
...mapActions("books", ["setBooks"]),
}
},
created: async function() {
await this.setBooks();
}
The problem is that my test can't find the action:
import { shallowMount } from '#vue/test-utils';
import Books from '#/views/Books';
import Vuex from 'vuex';
import flushPromises from 'flush-promises';
import { createLocalVue } from '#vue/test-utils'
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Books', () => {
let actions;
let store;
let wrapper;
beforeEach(() => {
store = new Vuex.Store({
state: {
books: {
books: null
}
},
actions: {
setBooks: jest.fn()
}
});
wrapper = shallowMount(Books, { store, localVue })
});
it("renders the books", async () => {
await flushPromises();
expect(actions.setBooks).toHaveBeenCalled();
});
});
I get an error [vuex] module namespace not found in mapActions(): books/
if I try to namespace the actions code in the my test to:
actions: {
books: {
setBooks: jest.fn()
}
}
I get TypeError: Cannot read property 'setBooks' of undefined
Please help, thank you!
The docs for vue-test-utils include an example of testing with modules.
Change your beforeEach:
beforeEach(() => {
actions = { setBooks: jest.fn() }
store = new Vuex.Store({
modules: {
books: {
state: { books: null },
actions
}
}
})
...
})
Your test calls actions.setBooks, but in your original code actions was simply declared, but not used in the creation of your store.

Can't seem to test Vuex Mutation in Vue Component

What should be pretty simple seems to be alluding me...
I have a vue component that when the correct html tag is clicked, a method is run that triggers a commit mutation.
All I want to do is see that the mutation is indeed triggered in the component. I can see the method get triggered but not the mutation that the method calls.
I am using Jest for testing
Goal: Verify the 'setEditUser' commit is called.
Dashboard
...
<tr
id="editUserTr"
class="col1"
v-for="listUserĀ inĀ users"
v-bind:key="listUser.email"
#click="editUser(listUser)"
>
<td>{{listUser.id}}</td>
<td>{{listUser.first_name}}</td>
...
methods: {
editUser(listUser) {
this.$store.commit("setEditUser", listUser);
this.$router.push("/editUser");
},
dashboardTest.spec.js
import { shallowMount, createLocalVue } from '#vue/test-utils';
import Dashboard from '../../src/components/Dashboard.vue';
import VueRouter from 'vue-router';
import Vuex from 'vuex';
const fb = require('../../src/firebaseConfig.js');
const sinon = require('sinon');
const localVue = createLocalVue();
localVue.use(Vuex, VueRouter);
describe('Dashboard.js', () => {
beforeEach(() => {
const mutations = {
setEditUser: jest.fn(),
};
const getters = {
userProfile: () => jest.fn(),
users: () => jest.fn(),
};
const $router = {
push: jest.fn(),
};
const store = new Vuex.Store({ mutations, getters });
});
test('Edit User function does not error out', () => {
const userProfile = {
role: ['admin'],
};
const wp = shallowMount(Dashboard, {
store,
localVue,
mocks: {
$router,
},
computed: {
userProfile() {
return userProfile;
},
users() {
return {
users: {
id: 'someEmail#email.com',
first_name: 'Stub',
},
};
},
},
});
const userInfo = wp.vm.users;
const stub = sinon.stub(wp.vm, 'editUser');
wp.findAll('td').at(0).trigger('click');
expect(mutations.setEditUser).toHaveBeenCalledWith({}, {userInfo}) // Fails. Says: Number of calls = 0
expect(stub.callCount).toBe(1);// Passes
});
});
Thanks for your help!
Edit:
Figured it out....
When I create this constant
const stub = sinon.stub(wp.vm, 'editUser');
I am messing with how the commit and router get called. Probably not passing in the proper arguments to the stub. I tried adding the proper arguments to the stub but still no go.
If I remove the stub then I can verify that the commit and push methods get called.
So... I created another unit test that just test that. Its more code then what I would like but I did solve my issue for now :)