How to test a VueJS (Nuxt) Store in Jest - vue.js

I have a Jest test that is testing my VueJs component (specifically Nuxt, but not important for this). I am attempting to mock a store that is an JSON Object. I for the life of me can not figure out how to test this. The error I keep getting when I run my test is "Cannot read propery 'easg_logo' of undefined".
My Vue component (footer.vue)
<template>
<div>
<v-img
:height="easg_logo_height"
:src="$store.state.app.easg_logo.src"
:width="easg_logo_width"
contain
/>
<v-img
:height="oma_logo_height"
:src="$store.state.app.oma_logo.src"
:width="oma_logo_width"
contain
/>
</div>
</template>
<script>
export default {
data(){
easg_logo_width: this.$store.state.app.easg_logo.top.width,
easg_logo_height: this.$store.state.app.easg_logo.top.height,
oma_logo_width: this.$store.state.app.oma_logo.top.width,
oma_logo_width: this.$store.state.app.oma_logo.top.width,
}
}
</script>
My test (footer.test.js)
import {shallowMount, createLocalVue} from '#vue/test-utils'
import Vuex from 'vuex';
import Footer from '#components/layouts/default/footer'
import Vuetify from 'vuetify';
const vuetify = new Vuetify();
const localVue = createLocalVue();
localVue.use(Vuex);
describe("Footer tests", ()=> {
let wrapper;
let store;
let state;
beforeEach(() => {
state= {
app: {
easg_logo:{
src: "~/assets/images/easg.jpg",
text: "EASG",
top:{
height: 72,
width: 82
}
},
oma_logo:{
src: "~/assets/images/oma.jpg",
text: "OMA",
top:{
height: 72,
width: 82
}
}
}
}
store = new Vuex.Store({
modules:{
state
}
})
})
test('store test', ()=> {
wrapper = shallowMount(Footer, {store, localVue, vuetify})
console.log(wrapper)
const a = 'a'
expect(a).toBe('a')
});
});

There is no state because state was mistakenly provided as a module.
It should be:
store = new Vuex.Store({
state
})

Related

How can I test the focus of a text field in a Vue component?

I am new to Vue and I've been asked to implement tests for some components.
This is the component:
<template>
<v-form>
<v-container>
<v-text-field
v-on:keydown.enter.prevent
v-model="filterName"
dense
clearable
style="transition: all 0.3s ease; width: 12rem;"
class="mt-6"
#focus="isFocused = true"
#blur="isFocused = false"
ref='filter'
>
</v-text-field>
</v-container>
</v-form>
</template>
<script>
export default {
name: "FilterBox",
data: () => ({
isFocused: false,
}),
computed: {
filterName: {
get() {
return this.$store.getters.filter.name
},
set(value) {
this.$store.commit('set_filter_name', value)
}
}
},
mounted() {
this.$refs.filter.focus()
},
}
</script>
This is the test that I implemented for that component:
import Vue from "vue";
import Vuex from "vuex";
import Vuetify from "vuetify";
import { shallowMount } from "#vue/test-utils";
import FilterBox from "#/components/FilterBox";
Vue.use( Vuex );
Vue.use( Vuetify );
describe( 'Filter Box', () => {
let store;
let getters;
let vuetify;
beforeEach( () => {
vuetify = new Vuetify();
} );
it( 'should return a valid wrapper of the component', function () {
getters = {};
store = new Vuex.Store( { getters } );
const wrapper = shallowMount( FilterBox, {
store,
vuetify
} );
expect( wrapper.exists() ).toBe( true );
} );
} );
But when I run it, I keep getting this error:
Cannot read properties of undefined (reading 'focus')
TypeError: Cannot read properties of undefined (reading 'focus')
I tried this approach:
getters = {
filter: () => {
return jest.fn()
}
};
store = new Vuex.Store( { getters } );
But then I got this error:
this.$refs.filter.focus is not a function
TypeError: this.$refs.filter.focus is not a function
Can anyone help me figure out what I'm doing wrong here? How can I test that the text field is focused on mount hook?
Try using mount(...) instead of shallowMount(...) for the tests that are testing the focus.
In your case #focus and #blur are events provided by v-textfield and accessed via ref.
Using shallowMount you don't actually render v-textfield but a stub instead.
This is useful in tests where you don't want to test the implementation of your sub-componentsn

Jest : TypeError: Cannot read property 'variable' of undefined

I am testing though Jest on the Vue 2.x, nuxtjs and #nuxtjs/composition-api.
However, the state value in the components has undefined value when testing though jest
List.spec.js
import Vue from 'vue';
import Vuetify from 'vuetify';
import { createLocalVue, shallowMount } from '#vue/test-utils';
import List from '#/components/home/list.vue';
Vue.use(Vuetify);
describe('List.vue', () => {
const localVue = createLocalVue();
let vuetify;
const $t = () => {};
const localePath = () => {};
beforeEach(() => {
vuetify = new Vuetify();
localVue.use(vuetify);
});
const mockOrder = [
{
coardshare: {
cs_id: 123,
},
},
{
talkboard: {
cs_id: 123,
},
},
];
it('11111', () => {
const wrapper = shallowMount(List, {
localVue,
vuetify,
propsData: { data: mockOrder },
mocks: { $t, localePath },
data() {
return {
data: mockOrder,
};
},
});
expect(wrapper.html()).toMatchSnapshot();
const title = wrapper.find('.v-card__title > span');
expect(title.text()).toBe('Foobar');
});
});
List.vue
<template>
...
<div v-for="item in state.data.talkboard" :key="item.cs_id">
<ListItem :item="item"></ListItem>
</div>
...
</template>
<script>
import { reactive, onMounted, useContext } from '#nuxtjs/composition-api';
import axios from 'axios';
import Header from './header';
import ListItem from './list-item.vue';
export default {
name: 'ListHome',
components: {
Header,
ListItem,
},
setup() {
const state = reactive({
data: [],
});
const { store } = useContext();
const fatch = async () => {
....
};
onMounted(fatch);
return {
state,
fatch,
};
},
};
</script>
error message
TypeError: Cannot read property 'data' of undefined
I am testing though Jest on the Vue 2.x, nuxtjs and #nuxtjs/composition-api.
However, the state value in the components has undefined value when testing though jest
why error on this ?? because of composition API that define the state with reactive() function ??
In your test file maybe you can try something like this:
it('11111', () => {
const wrapper = shallowMount(List, {
localVue,
vuetify,
propsData: { data: mockOrder },
mocks: { $t, localePath },
data: () => {
return {
data: mockOrder,
};
},
});

Unknown custom element: - did you register the component correctly? error on with <nuxt /> component in default.vue Jest

I'm trying to write tests for default.vue file which has the following code:
default.vue
<template>
<div>
<top-nav :class="isSticky ? 'fixed-top stickyAnimate' : ''" />
<main>
<nuxt />
</main>
<footer />
</div>
</template>
<script>
import TopNav from '../components/TopNav.vue';
import Footer from '../components/Footer.vue';
import StickyNavMixin from '../mixins/stickyNavMixin';
export default {
components: {
TopNav,
Footer,
},
mixins: [StickyNavMixin],
data() {
return {
loading: true,
};
},
mounted() {
if (!window.location.hash) {
this.loading = false;
}
},
};
</script>
then my test look like this
default.spec.js
import { createLocalVue, shallowMount } from '#vue/test-utils';
import BootstrapVue from 'bootstrap-vue';
import StickyNavMixin from '../mixins/stickyNavMixin';
import Default from '../layouts/default.vue';
import TopNav from '../components/TopNav.vue';
import Footer from '../components/Footer.vue';
const localVue = createLocalVue();
localVue.use(BootstrapVue);
localVue.mixin(StickyNavMixin);
describe('Default', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(Default, {
localVue,
});
});
test('is a Vue instance', () => {
expect(wrapper.isVueInstance()).toBeTruthy();
});
test('has navbar component', () => {
expect(wrapper.find(TopNav).exists()).toBe(true);
});
});
When I ran this test, I get error says:
[Vue warn]: Unknown custom element: - did you register the component correctly? For
recursive components, make sure to provide the "name" option.found in --->
Please guide me to a right direction. Thank you in advance!
I figured out how to get past that error. I had to just stub it out of the wrapper. You don't have to import Nuxt, just string 'nuxt' will replace it as a stubbed element in the wrapper:
describe('DefaultLayout', () => {
let wrapper;
afterEach(() => {
wrapper.destroy();
});
/** mount **/
test('is a Vue instance', () => {
wrapper = mount(DefaultLayout, {
localVue,
stubs: ['nuxt'],
});
expect(wrapper.isVueInstance()).toBeTruthy();
});
/** shallowMount **/
test('is a Vue instance', () => {
wrapper = shallowMount(DefaultLayout, {
localVue,
stubs: ['nuxt', 'top-nav', 'footer'],
});
expect(wrapper.isVueInstance()).toBeTruthy();
// expect(wrapper.html()).toBe('<div>'); => this is to debug see below for output
});
});
//DEBUG
"<div><top-nav-stub class=\"\"></top-nav-stub> <main><nuxt-stub></nuxt-stub> .
</main> <footer-stub></footer-stub></div>"

vuex unknown local mutation type: updateValue, global type: app/updateValue. Mutations don't work

I want to apply mutations through actions to a variable in my vuejs application. But I get this error saying [vuex] unknown local mutation type: updateValue, global type: app/updateValue
Here is my store folder structure:
-store
-modules
-app
-actions.js
-getters.js
-mutations.js
-state.js
-index.js
-actions.js
-getters.js
-mutations.js
-state.js
-index.js
This is my ./store/index.js file:
import Vue from 'vue'
import Vuex from 'vuex'
import actions from './actions'
import getters from './getters'
import modules from './modules'
import mutations from './mutations'
import state from './state'
Vue.use(Vuex)
const store = new Vuex.Store({
namespaced: true,
actions,
getters,
modules,
mutations,
state
})
export default store
This is my ./store/modules/index.js:
const requireModule = require.context('.', true, /\.js$/)
const modules = {}
requireModule.keys().forEach(fileName => {
if (fileName === './index.js') return
// Replace ./ and .js
const path = fileName.replace(/(\.\/|\.js)/g, '')
const [moduleName, imported] = path.split('/')
if (!modules[moduleName]) {
modules[moduleName] = {
namespaced: true
}
}
modules[moduleName][imported] = requireModule(fileName).default
})
export default modules
This is my ./store/modules/app/actions.js:
export const updateValue = ({commit}, payload) => {
commit('updateValue', payload)
}
This is my ./store/modules/app/getters.js:
export const value = state => {
return state.wruValue;
}
This is my ./store/modules/app/mutations.js:
import { set, toggle } from '#/utils/vuex'
export default {
setDrawer: set('drawer'),
setImage: set('image'),
setColor: set('color'),
toggleDrawer: toggle('drawer')
}
export const updateValue = (state, payload) => {
state.wruValue = payload * 12;
}
This is my ./store/modules/app/state.js:
export default {
drawer: null,
color: 'green',
wruValues:1,
wruValue: 1,
}
and finally this is my vue component:
<v-btn #click="updateValue(10)">
SHOW
</v-btn>
import { mapActions } from 'vuex';
...mapActions ('app',[
'updateValue'
]),
So when I click on the button I expect to see the wruValue to change (I print the value somewhere else for testing purposes) but instead I get the error mentioned above. What's wrong with my code?
commit('updateValue', payload, {root: true})
But I find your use of namespacing odd. For my projects, I don't separate out files for getters, actions, etc, I separate out tasks, projects, companies, etc. But if it works for you, that's fine. It doesn't seem like the issue. If you still get an error, you might need to change "updateValue" to "mutations/updateValue" or something.
You should use this project structure:
src/store/modules/app.js
export const state = {
drawer: null,
color: 'green',
wruValues: 1,
wruValue: 1
}
export const mutations = {
UPDATE_VALUE: (state, payload) => {
state.wruValue = payload * 12
}
}
export const actions = {
updateValue: ({ commit }, payload) => {
commit('UPDATE_VALUE', payload)
}
}
export const getters = {
getWruValue: (state) => state.wruValue
}
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const requireContext = require.context('./modules', true, /.*\.js$/)
const modules = requireContext.keys()
.map(file =>
[file.replace(/(^.\/)|(\.js$)/g, ''), requireContext(file)]
)
.reduce((modules, [name, module]) => {
if (module.namespaced === undefined) {
module.namespaced = true
}
return { ...modules, [name]: module }
}, {})
export default new Vuex.Store({
modules
})
src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store/'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
src/App.vue
<template>
<div id="app">
<button #click="updateValue(10)">
SHOW
</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions('app', ['updateValue'])
}
}
</script>
Then if you want to add a new store namespace, u need to put it inside of src/modules folder.

Vue-Test-Utils Unknown custom element: <router-link>

I'm using Jest to run my tests utilizing the vue-test-utils library.
Even though I've added the VueRouter to the localVue instance, it says it can't actually find the router-link component. If the code looks a little funky, it's because I'm using TypeScript, but it should read pretty close to ES6... Main thing is that the #Prop() is the same as passing in props: {..}
Vue component:
<template>
<div>
<div class="temp">
<div>
<router-link :to="temp.url">{{temp.name}}</router-link>
</div>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
import { Prop } from 'vue-property-decorator'
import { Temp } from './Temp'
#Component({
name: 'temp'
})
export default class TempComponent extends Vue {
#Prop() private temp: Temp
}
</script>
<style lang="scss" scoped>
.temp {
padding-top: 10px;
}
</style>
Temp model:
export class Temp {
public static Default: Temp = new Temp(-1, '')
public url: string
constructor(public id: number, public name: string) {
this.id = id
this.name = name
this.url = '/temp/' + id
}
}
Jest test
import { createLocalVue, shallow } from '#vue/test-utils'
import TempComponent from '#/components/Temp.vue'
import { Temp } from '#/components/Temp'
import VueRouter from 'vue-router'
const localVue = createLocalVue()
localVue.use(VueRouter)
describe('Temp.vue Component', () => {
test('renders a router-link tag with to temp.url', () => {
const temp = Temp.Default
temp.url = 'http://some-url.com'
const wrapper = shallow(TempComponent, {
propsData: { temp }
})
const aWrapper = wrapper.find('router-link')
expect((aWrapper.attributes() as any).to).toBe(temp.url)
})
})
What am I missing? The test actually passes, it just throws the warning. In fact, here is the output:
Test Output:
$ jest --config test/unit/jest.conf.js
PASS ClientApp\components\__tests__\temp.spec.ts
Temp.vue Component
√ renders a router-link tag with to temp.url (30ms)
console.error node_modules\vue\dist\vue.runtime.common.js:589
[Vue warn]: Unknown custom element: <router-link> - did you register the
component correctly? For recursive components, make sure to provide the
"name" option.
(found in <Root>)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.677s
Ran all test suites.
Done in 6.94s.
Appreciate any help you can give!
Add the router-link stub to the shallow (or shallowMount) method options like this:
const wrapper = shallow(TempComponent, {
propsData: { temp },
stubs: ['router-link']
})
or this way:
import { RouterLinkStub } from '#vue/test-utils';
const wrapper = shallow(TempComponent, {
propsData: { temp },
stubs: {
RouterLink: RouterLinkStub
}
})
The error should go away after you do this.
With Vue 3 and Vue Test Utils Next (v4), it seems you just have to add your router (the return object from createRouter) as a plugin to your mountOptions:
import router from "#/router";
const mountOptions = {
global: {
plugins: [router],
},
};
https://next.vue-test-utils.vuejs.org/api/#global
Or a more full example:
import router from "#/router";
import Button from "#/components/Button.vue";
const mountOptions = {
global: {
mocks: {
$route: "home",
$router: {
push: jest.fn(),
},
},
plugins: [router],
},
};
it("Renders", () => {
const wrapper = shallowMount(Button, mountOptions);
expect(wrapper.get("nav").getComponent({ name: "router-link" })).toExist();
});
Note, in the example above I'm using a project setup with Vue CLI.
Worked for me:
[ Package.json ] file
...
"vue-jest": "^3.0.5",
"vue-router": "~3.1.5",
"vue": "~2.6.11",
"#vue/test-utils": "1.0.0-beta.29",
...
[ Test ] file
import App from '../../src/App';
import { mount, createLocalVue } from '#vue/test-utils';
import VueRouter from 'vue-router';
const localVue = createLocalVue();
localVue.use(VueRouter);
const router = new VueRouter({
routes: [
{
name: 'dashboard',
path: '/dashboard'
}
]
});
describe('Successful test', ()=>{
it('works', ()=>{
let wrapper = mount(App, {
localVue,
router
});
// Here is your assertion
});
});
Or you can try this:
const wrapper = shallow(TempComponent, {
propsData: { temp },
localVue
})