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');
})
})
Related
I've tried a lot of things but $route seems to always be undefined and I have this error on shallowMount: TypeError: Cannot read property 'params' of undefined.
In my component, I want to check the params of the $route but it is always undefined. I've looked the documentation and mocked the $route to set it but it seems like the $route is not mocked. I've also tried using localVue.use(VueRouter), thinking that it would set $route but it didn't. Does anyone have an idea why the following doesn't work in both cases?
my-component.ts
#Component
export default class MyComponent extends Vue {
get organisationId() {
return this.$route.params.organisationId;
}
}
I've tried the 2 following tests with the solutions I've talked about:
my-component.spec.ts
import { createLocalVue, shallowMount } from '#vue/test-utils';
import MyComponent from '#/components/MyComponent.vue';
import router from '#/router';
const localVue = createLocalVue();
describe('MyComponent', () => {
let cmp: any;
beforeEach(() => {
cmp = shallowMount(MyComponent, {
localVue,
router,
mocks: {
$route: {
params: {
id: 'id'
}
}
}
});
});
it('Should render a Vue instance', () => {
expect.assertions(1);
expect(cmp.isVueInstance()).toBeTruthy();
});
});
my-component.spec.ts
import { createLocalVue, shallowMount } from '#vue/test-utils';
import MyComponent from '#/components/MyComponent.vue';
const localVue = createLocalVue();
describe('MyComponent', () => {
let cmp: any;
localVue.use(VueRouter);
const router = new VueRouter();
beforeEach(() => {
cmp = shallowMount(MyComponent, {
localVue,
router
});
});
it('Should render a Vue instance', () => {
expect.assertions(1);
expect(cmp.isVueInstance()).toBeTruthy();
});
});
You were mocking $route right in #1 test. You can access $route property of test component via wrapper.vm.$route (or cmp.vm.$route in your case). And There is example:
import { shallowMount, Wrapper } from '#vue/test-utils'
import MyComponent from '#/components/MyComponent.vue'
let wrapper: Wrapper<MyComponent>
describe('MyComponent', () => {
beforeEach(() => {
wrapper = shallowMount(MyComponent, {
mocks: {
$route: {
params: {
id: 'id'
}
}
}
})
})
it('$route has passed params', () => {
expect(wrapper.vm.$route.params.id).toBe('id')
})
})
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.
I want to test a vuex module called user.
Initially, I successfully registered my module to Vuex. Its works as expected.
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user
}
})
export default store
My user module is defined as follows
store/modules/user.js
const state = {
token: getToken() || '',
}
export const getters = {
token: state => state.token,
}
const mutations = {
[SET_TOKEN]: (state, token) => {
state.token = token
}
}
const actions = {
[LOGIN] ({ commit }, body) {
return new Promise((resolve, reject) => {
login(body).then(response => { //login is an api method, I'm using axios to call it.
const { token } = response.data
setToken(token)
commit(SET_TOKEN, token)
resolve()
}).catch(error => {
reject(error)
})
})
}
}
export default {
state,
getters,
mutations,
actions
}
login api
api/auth.js
import request from '#/utils/request'
export function login (data) {
return request({
url: '/auth/login',
method: 'post',
data
})
}
axios request file
utils/request
import axios from 'axios'
import store from '#/store'
import { getToken } from '#/utils/auth'
const request = axios.create({
baseURL: process.env.VUE_APP_BASE_API_URL,
timeout: 5000
})
request.interceptors.request.use(
config => {
const token = getToken()
if (token) {
config.headers['Authentication'] = token
}
return config
}
)
export default request
When I want to write some test (using Jest), for example login action as shown above.
// user.spec.js
import { createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import actions from '#/store/modules/user'
const localVue = createLocalVue()
localVue.use(Vuex)
test('huhu', () => {
expect(true).toBe(true)
// implementation..
})
How can I write test for my Login action? Thanks. Sorry for my beginner question.
EDIT: SOLVED Thank you Raynhour for showing to me right direction :)
import { LOGIN } from '#/store/action.types'
import { SET_TOKEN } from '#/store/mutation.types'
import { actions } from '#/store/modules/user'
import flushPromises from 'flush-promises'
jest.mock('#/router')
jest.mock('#/api/auth.js', () => {
return {
login: jest.fn().mockResolvedValue({ data: { token: 'token' } })
}
})
describe('actions', () => {
test('login olduktan sonra tokeni başarıyla attı mı?', async () => {
const context = {
commit: jest.fn()
}
const body = {
login: 'login',
password: 'password'
}
actions[LOGIN](context, body)
await flushPromises()
expect(context.commit).toHaveBeenCalledWith(SET_TOKEN, 'token')
})
})
Store it's just a javascript file that will export an object. Not need to use vue test util.
import actions from '../actions'
import flushPromises from 'flush-promises'
jest.mock('../api/auth.js', () => {
return {
login: jest.fn()..mockResolvedValue('token')
}; // mocking API.
describe('actions', () => {
test('login should set token', async () => {
const context = {
commit: jest.fn()
}
const body = {
login: 'login',
password: 'password'
}
actions.login(context, body)
await flushPromises() // Flush all pending resolved promise handlers
expect(context.commit).toHaveBeenCalledWith('set_token', 'token')
})
})
but you need to remember that in unit tests all asynchronous requests must be mocked(with jest.mock or something else)
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 :)
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