How to access namespaced Vuex getters in Mocha unit test - unit-testing

I am building a new Vue component that uses a namespaced Vuex getter to access a list of column names. The actual component compiles and runs.
In my Mocha unit tests, I created a mocked getter that returns a list of strings called "allColumns". When I run the unit tests, during ShallowMount, the component's methods try to access this.allColumns during initialization, but the value is always undefined. I can see the value I want in this.$store.getters.allColumns, but it is not getting mapped to this.allColumns like it does when I open the page in a browser.
There is a lot of information out there about how to mock getters in a test and how to use mapGetters with a namespace, but I have not found any documentation about namespaced getters in a Mocha test.
test.spec.js
let propsData;
let getters;
let store;
beforeEach(() => {
debugger;
propsData = {
players: samplePlayerObject,
metadata: sampleMetadataObject
};
getters = {
allColumns: () => ["playerid","last","first","birthday","height"]
}
store = new Vuex.Store({
getters
});
})
it('initializes the component', () => {
const wrapper = shallowMount(PlayerFilterTable, { propsData, localVue, store });
});
vue component
<template>
<div class="player-filter-table">
<table>
<tr>
<th v-for="(key, index) in GetColumns()"
v-bind:id="'header-' + key"
v-bind:key="index"
#click="HeaderClick(key)"
>...</th>
</tr>
</table>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters({
allColumns: 'playerFilter/allColumns'
})
},
GetColumns() {
// this.allColumns is defined when running in browser, but undefined when loaded from a Mocha test
return this.allColumns.filter(column => [*some filter criteria*]);
}
</script>
When shallowMount runs in test.spec.js, I expect the component to load successfully and then continue on to run my tests, but instead I get an error that says TypeError: Cannot read property 'filter' of undefined because this.allColumns is not defined.

Use modules with namespaced: true:
import { createLocalVue, shallowMount } from '#vue/test-utils';
import Vuex from 'vuex';
import PlayerFilterTable from '~/whatever';
const localVue = createLocalVue();
localVue.use(Vuex);
let propsData, getters, store, wrapper, consoleSpy;
describe('PlayerFilterTable', () => {
beforeEach(() => {
consoleSpy = jest.spyOn(console, 'error');
propsData = {
players: samplePlayerObject,
metadata: sampleMetadataObject
};
getters = {
allColumns: () => ["playerid", "last", "first", "birthday", "height"]
};
store = new Vuex.Store({
modules: {
playerFilter: {
namespaced: true,
getters
}
}
});
wrapper = shallowMount(PlayerFilterTable, {
propsData,
localVue,
store
});
});
afterEach(() => {
expect(consoleSpy).not.toHaveBeenCalled();
});
it('should render correctly', () => {
expect(wrapper.is(PlayerFilterTable)).toBe(true);
expect(wrapper.html()).toMatchSnapshot();
})
})
If you use getters from more than one module you could group them up under different props of getters and assign to each module accordingly.

Related

vue.js unit test v-slot with function call

I use bootstrap-vue for the vue.js css framework and decided to test the desired component. This component uses b-table and has a v-slot with a call function.
<template>
<b-table
striped
bordered
:items="items"
:fields="$t('pages.events.show.users.fields')"
>
<template v-slot:cell(name)="{ item }">
<b-avatar :src="item.avatar" class="mr-2" />
<span v-text="item.name" />
</template>
</b-table>
</template>
and I'm writing a simple test for this component:
import { shallowMount } from "#vue/test-utils";
import EventUsersTable from "./EventUsersTable.vue";
/* #region Test setup */
const factory = () => {
return shallowMount(EventUsersTable, {
mocks: {
$t: jest.fn()
},
stubs: {
BTable: true
}
});
};
/* #endregion */
describe("EventUsersTable.vue", () => {
let wrapper;
beforeEach(() => (wrapper = factory()));
test("should render component", () => {
expect(wrapper.html()).toMatchSnapshot();
});
});
and i have error with this content: [Vue warn]: Error in render: "TypeError: Cannot read property 'item' of undefined"
for write test for this component i need fix this problem.
And I have a problem with the vue unit test document, they are very limited and with few examples.
If anyone knows a source that has more examples and scenarios for vue language tests, thank you for introducing it.
After inquiring, I came up with a solution that, using mount and adding the main component, was able to solve my problem.
import { mount } from "#vue/test-utils";
import { BTable } from "bootstrap-vue";
import EventUsersTable from "./EventUsersTable.vue";
/* #region Test setup */
const factory = () => {
return mount(EventUsersTable, {
mocks: {
$t: jest.fn()
},
stubs: {
BTable
}
});
};
/* #endregion */
describe("EventUsersTable.vue", () => {
let wrapper;
beforeEach(() => (wrapper = factory()));
// FIXME: fix this bug for render component
test("should render component", () => {
expect(wrapper.html()).toMatchSnapshot();
});
});

Testing that a method is called when component is mounted

I'm trying to test that a method gets called when a component is mounted but it keeps failing with
Expected mock function to have been called one time, but it was called zero times.
Here is the component:
<template>
<b-form-input
class="mr-2 rounded-0"
placeholder="Enter Search term..."
id="input-keyword"
/>
</template>
<script>
export default {
name: 'job-search-test',
methods: {
async searchJobs () {
console.log('Calling Search Jobs from JobsSearchTest')
}
},
mounted () {
this.searchJobs()
}
}
</script>
Here is the test:
import { shallowMount, createLocalVue } from '#vue/test-utils'
import BootstrapVue from 'bootstrap-vue'
import JobSearchTest from '#/components/jobs/JobSearchTest'
const localVue = createLocalVue()
localVue.use(BootstrapVue)
describe('JobsSearchTest.vue', () => {
it('should call searchJobs method when component is mounted', () => {
const methods = {
searchJobs: jest.fn()
}
shallowMount(JobSearchTest, {
mocks: {
methods
},
localVue })
expect(methods.searchJobs).toHaveBeenCalledTimes(1)
})
})
However, the following test passes
import { shallowMount, createLocalVue } from '#vue/test-utils'
import BootstrapVue from 'bootstrap-vue'
import JobSearchTest from '#/components/jobs/JobSearchTest'
const localVue = createLocalVue()
localVue.use(BootstrapVue)
describe('JobsSearchTest.vue', () => {
it('should call searchJobs method when component is mounted', () => {
let searchJobs = jest.fn()
shallowMount(JobSearchTest, {
methods: {
searchJobs
},
localVue })
expect(searchJobs).toHaveBeenCalledTimes(1)
})
})
According to Testing VueJs Applications by Edd Yerburgh one tests a function by stubbing it with a Jest mock the following way
it('should call $bar.start on load', () => {
const $bar = {
start: jest.fn(),
finish: () => {}
}
shallowMount(ItemList, { mocks: $bar })
expect($bar.start).toHaveBeenCalledTimes(1)
})
In my eyes, this is essentially what I am doing in the first test, which fails.
Any help with why this could be happening will be appreciated.
mocks option mocks instance properties. mocks: { methods } assumes that there's methods property in Vue component. Since this.methods.searchJobs() isn't called, the test fails.
It's searchJobs method, the test should be as the working snippet shows:
shallowMount(JobSearchTest, {
methods: {
searchJobs
},
localVue })

vue-test-utils: How to test logic within mounted() lifecycle hook (with vuex)?

I'm trying to write a unit test for the logic within Vue's mounted() lifecycle hook, but not having much luck. The problem seems to be that mounted() never gets called when the component is mounted using vue-test-utils mount. Here's the Vue component I'm trying to test:
<template>
<div></div>
</template>
<script>
export default {
name: 'MyComponent',
mounted () {
this.$store.dispatch('logout')
}
}
</script>
And the test itself:
import { mount, createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import MyComponent from '#/components/MyComponent'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('MyComponent.vue', () => {
let store
let actions
beforeEach(() => {
actions = {
logout: jest.fn().mockName('logout')
}
store = new Vuex.Store({
state: {},
actions
})
})
it('calls store "logout" action', () => {
mount(MyComponent, { localVue, store })
expect(actions.logout).toHaveBeenCalled()
})
})
However, this fails with expect(logout).toHaveBeenCalled() asserting false.
If I call the mocked store action directly with actions.logout() the test passes, and I have other tests which also call store actions on things like a button press, and those pass as well, so the problem definitely appears to be with the mounted() lifecycle hook.
Any thoughts?
(vue 2.5.4 and vue-test-utils 1.0.0-beta-.15)
Not sure how it's any different, but I abstracted the store mock to another file and everything seems to work now.
mocks.js
export const storeMock = Object.freeze({
state: {},
actions: {
logout: jest.fn().mockName('logout')
},
})
test.spec.js
import { shallowMount, createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import { storeMock } from './mocks.js'
import MyComponent from '#/components/MyComponent'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('MyComponent.vue', () => {
let options
beforeEach(() => {
jest.clearAllMocks()
const store = new Vuex.Store(storeMock)
options = { store, localVue }
})
it('calls store "logout" action', () => {
shallowMount(MyComponent, options)
expect(storeMock.actions.logout).toHaveBeenCalled()
})
})
Without abstracting the store mock to another file, and slightly different approach without beforeEach (ruined my tests for some reason).
import { createLocalVue, shallowMount } from "#vue/test-utils";
import Vuex from "vuex";
import MyComponent from "#/components/MyComponent.vue";
describe("MyComponent", () => {
const localVue = createLocalVue();
localVue.use(Vuex);
const actions = {
logout: jest.fn()
};
const store = new Vuex.Store({ actions });
const wrapper = shallowMount(MyComponent, {
localVue,
store
});
it('calls store "logout" action', () => {
expect(actions.logout).toHaveBeenCalled();
});
});

vue-test-utils: mocking vue-router and vuex in the same test

I'm trying to mount a component that uses Vuex and requires $route.query to be mocked
import { mount, shallow, createLocalVue } from 'vue-test-utils'
import Vue from 'vue'
import expect from 'expect'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import Post from '../../components/Post.vue'
const localVue = createLocalVue()
localVue.use(Vuex);
localVue.use(VueRouter);
describe('Lead list', () => {
let wrapper;
let getters;
let store;
beforeEach(() => {
getters = {
post: () => { return {} }
}
store = new Vuex.Store({
getters
});
});
it('should just be true', () => {
const $route = {
path: '/some/path',
query: {}
}
wrapper = shallow(Post, {
localVue,
mocks: {
$route
}, store
});
expect(true).toBe(true);
});
});
And I'm getting back this error
TypeError: Cannot set property $route of #<VueComponent> which has only a getter
I've found the closed issue https://github.com/vuejs/vue-test-utils/issues/142 that has similar error. But my case is a little different. If I remove store or mocks from the options it works fine, but it does't work when you have both. Is this an issue or I'm doing something wrong?
Thanks
You're getting this error because you have installed VueRouter on the Vue constructor, by calling localVue.use(VueRouter). This adds $route as a read only property on the localVue constructor.
You're then trying to overwrite $router using mocks. mocks is unable to overwrite $route because it's been added as a read only property by Vue Router.
To fix your problem, you could create another localVue, install Vuex, and then use mocks to pass in $route:
it('should just be true', () => {
const freshLocalVue = createLocalVue()
freshLocalVue.use(Vuex)
const $route = {
path: '/some/path',
query: {}
}
wrapper = shallow(Post, {
localVue,
mocks: {
$route
},
store
})
expect(true).toBe(true)
})

Unit test vue.js component with inline-template

I am using Vue 2 to enhance a Ruby on Rails engine, using inline-template attributes in the existing Haml views as templates for my Vue components.
Is it possible to test the methods of a component defined like this? All the testing examples I can find assume the use of single-file .vue components.
These tests (using Mocha and Chai) fail with [Vue warn]: Failed to mount component: template or render function not defined.
Example Component:
//main-nav.js
import Vue from 'vue'
const MainNav = {
data: function() {
return {open: true}
},
methods: {
toggleOpen: function(item) {
item.open = !item.open
}
}
}
export default MainNav
Example Test:
//main-nav.test.js
import MainNav from '../../admin/main-nav'
describe('MainNav', () => {
let Constructor
let vm
beforeEach(() => {
Constructor = Vue.extend(MainNav)
vm = new Constructor().$mount()
})
afterEach(() => {
vm.$destroy()
})
describe('toggleOpen', () => {
it('has a toggleOpen function', () => {
expect(vm.MainNav.toggleOpen).to.be.a('function')
})
it('toggles open from true to false', () => {
const result = MainNav.toggleOpen({'open': true})
expect(result).to.include({open: false})
})
})
})
It turns out you can still specify a template in the component file, and any inline-template templates will be used in favour of that.