Jest Vuex: Error in created hook: "TypeError: Cannot read property 'dispatch' of undefined" - vue.js

I am currently trying to mock out the two Vuex actions that is called in the created() Vue hook, however, Jest keeps returning "TypeError: Cannot read property 'dispatch' of undefined", this is what I have tried:
Test file:
let store
let actions
beforeEach(() => {
actions = {
setApiUrl: jest.fn(),
init: jest.fn()
}
store = new Vuex.Store({
modules: {
membership: membershipTestData
},
actions
})
})
const wrapper = shallowMount(
Component,
store,
localVue
)
await wrapper.vm.setApiUrl('')
await wrapper.vm.init()
expect(actions.setApiUrl).toHaveBeenCalled()
expect(actions.init).toHaveBeenCalled()
Component file:
created () {
this.setApiUrl('')
this.init()
},
methods: {
...mapActions('membership', ['init', 'setApiUrl'])
}
Please can anyone suggest what I am doing wrong here, I have tried everything I could, but the test still fails due to the created() hook error.

I have solved it, where I went wrong was the in the wrapper, which should be (notice the diff in curly brace)
const wrapper = shallowMount(Component, {
localVue,
propsData
})

Related

Vue js, cannot read property $el

I have the problem with correctly understood the flow elements, method calling in vue js. It is the standard idea - fetching some data from rest api, and render it on the browser.
The getting method I wrote into mounted(). Also I added there calling renderHomePageMethod(). This method was written in methods:
mounted() {
axios.get("http://localhost:3000/api/test").then(response => {
this.testData= response.data
this.renderHomePageMethod();
});
}
In renderHomePageMethod() I used this.refs$ and $el. And probably there is the problem, everything is working fine, but in the browser I got warning about:
Uncaught (in promise) TypeError: Cannot read property '$el' of undefined
Probably I should calling
this.renderHomePageMethod()
in another place. But where?
It seems like your referenced component is not rendered before the main component renders, so it gives a reference error.
A hackish way would be something like this:
mounted() {
axios.get("http://localhost:3000/api/test").then(response => {
this.testData= response.data
setTimeout(() => {
this.renderHomePageMethod();
}, 1000); // or any other minimum delay before the subcomponent is rendered
});
}
or the better and harder way, create an event-bus.js file which contains:
import Vue from 'vue';
export const EventBus = new Vue();
in your main and sub components:
import { EventBus } from "./event-bus.js";
in your sub component, this will send the notification to the main component when it's ready to roll:
mounted(){
EventBus.$emit("subcomponent:is-mounted");
}
in your main component:
data(){
return {
testData: null
}
},
mounted(){
axios.get("http://localhost:3000/api/test").then(response => {
this.testData= response.data
});
EventBus.$on("subcomponent:is-mounted", () =>{
this.renderHomePageMethod();
});
},
beforeDestroy(){
EventBus.$off("subcomponent:is-mounted");
// don't forget to remove the listeners because of duplicate listeners may occur
// if your component refreshes (remounts)
}

Jest + Nuxt + Nuxt-Fire is failing in test suite

I'm using Nuxt with Nuxt-Fire (https://github.com/lupas/nuxt-fire)
When I launch my test I get this error [Vue warn]: Error in config.errorHandler: "TypeError: Cannot read property 'ref' of undefined"
This is happening because of this section in my App
mounted() {
this.initiate(window.instaroomId)
let connected = this.$fireDb.ref(".info/connected")
this.getConnection(connected)
},
It looks like the this.$fireDb is not called. The module is normally loaded in nuxt.config.js. How can I make this work?
If you want to test, that this.$fireDb.ref(".info/connected") was called you can mock it like this:
import { shallowMount } from '#vue/test-utils'
import SomeComponent from '#/components/SomeComponent/SomeComponent.vue'
let wrapper
describe('SomeComponent.vue Test', () => {
beforeEach(() => {
wrapper = shallowMount(SomeComponent, {
mocks: {
$fireDb: {
ref: jest.fn()
}
}
})
})
it('$fireDb.ref was called', () => {
expect(wrapper.vm.$fireDb.ref).toBeCalled()
expect(wrapper.vm.$fireDb.ref).toBeCalledWith('.info/connected')
})
})
Or if you want the test just to pass created() hook and test another functionality you can just mock $fireDb.ref without testing that it was called.

Accessing vue mixin on vuex action

I created vue mixin function that return the base url of the project. The reason I needed this mixin is because on the dev stage the value will come from configuration file, and on production stage it'll come from global variable window.
The backEndHost is accessible from any .vue file, but it's not accessible from vuex action.
Vue mixin declaration:
Vue.mixin({
data: () => ({
get backEndHost() {
// ...
return (isDev) devAddr : prodAddr
}
})
})
Vuex actions declaration:
const actions = {
getChallengesData({ commit, state }, task) {
// ...
axios.get(this.backEndHost() + url).catch((thrown) => {
swal('Error!', thrown.message, 'error')
}).then((res) => {
// ...
})
}
}
The axios.get(this.backEndHost() + url) triggered an error, saying that this.backEndHost is undefined.
[Vue warn]: Error in mounted hook: "TypeError: this.backEndHost is not a function"
Any idea how to resolve this error? or is there any workaround to achieve the same result?
I ended up putting the backEndHost into utility helper (thanks to #ittus). Since I still need the backEndHost to be available on all components, I also put it into mixin.
File: helper.js
export function backEndHost() {
return (isDev) devAddr : prodAddr
}
File: main.js
import { backEndHost } from '#/utility/helper'
Vue.mixin({
data: () => ({
backEndHost
})
})
File: actions.js
import { backEndHost } from '#/utility/helper'
const actions = {
getChallengesData({ commit, state }, task) {
// ...
axios.get(backEndHost() + url).catch((thrown) => {
swal('Error!', thrown.message, 'error')
}).then((res) => {
// ...
})
}
}

Vue test-utils how to test a router.push()

In my component , I have a method which will execute a router.push()
import router from "#/router";
// ...
export default {
// ...
methods: {
closeAlert: function() {
if (this.msgTypeContactForm == "success") {
router.push("/home");
} else {
return;
}
},
// ....
}
}
I want to test it...
I wrote the following specs..
it("should ... go to home page", async () => {
// given
const $route = {
name: "home"
},
options = {
...
mocks: {
$route
}
};
wrapper = mount(ContactForm, options);
const closeBtn = wrapper.find(".v-alert__dismissible");
closeBtn.trigger("click");
await wrapper.vm.$nextTick();
expect(alert.attributes().style).toBe("display: none;")
// router path '/home' to be called ?
});
1 - I get an error
console.error node_modules/#vue/test-utils/dist/vue-test-utils.js:15
[vue-test-utils]: could not overwrite property $route, this is usually caused by a plugin that has added the property asa read-only value
2 - How I should write the expect() to be sure that this /home route has been called
thanks for feedback
You are doing something that happens to work, but I believe is wrong, and also is causing you problems to test the router. You're importing the router in your component:
import router from "#/router";
Then calling its push right away:
router.push("/home");
I don't know how exactly you're installing the router, but usually you do something like:
new Vue({
router,
store,
i18n,
}).$mount('#app');
To install Vue plugins. I bet you're already doing this (in fact, is this mechanism that expose $route to your component). In the example, a vuex store and a reference to vue-i18n are also being installed.
This will expose a $router member in all your components. Instead of importing the router and calling its push directly, you could call it from this as $router:
this.$router.push("/home");
Now, thise makes testing easier, because you can pass a fake router to your component, when testing, via the mocks property, just as you're doing with $route already:
const push = jest.fn();
const $router = {
push: jest.fn(),
}
...
mocks: {
$route,
$router,
}
And then, in your test, you assert against push having been called:
expect(push).toHaveBeenCalledWith('/the-desired-path');
Assuming that you have setup the pre-requisities correctly and similar to this
Just use
it("should ... go to home page", async () => {
const $route = {
name: "home"
}
...
// router path '/home' to be called ?
expect(wrapper.vm.$route.name).toBe($route.name)
});

Vuejs testing with Mocha: component not mounting to element

I'm relatively new to developing with VueJS and testing with Mocha (being used headlessly) and I'm intentionally not using vue-cli to get a better understanding of how everything works. I have a suit of unit tests that function fairly well (fetch requests, getting/setting props, etc), up until the point where I need to check something in the DOM. It appears that the components are being successfully created, but they are not being mounted to an element.
I'm using JSDom to create a DOM context for the headless tests, as can be seen in my _setup.js:
global.Vue = require('vue/dist/vue');
require('jsdom-global')('<html><head></head><body><main id="MyApp"></main></body>');
In these cases, $el is undefined, despite the fact the above-defined DOM elements can be found:
app.spec.js
import assert from 'assert';
import app from '../src/js/app.vue';
describe('app.vue', function() {
describe('#created', function() {
it('should initialize the application', function() {
const vEl = document.getElementById('MyApp');
const vm = new Vue({el: vEl}).$mount();
console.log(vEl); // HTMLElement {}
console.log(vm.$el); // undefined
assert(true); // pass anyway for now
});
});
});
myComp.spec.js:
import assert from 'assert';
import header from '../src/js/components/myComp.vue';
describe('myComp.vue', function() {
describe('#created', function() {
it('should initialize the component', function() {
const Constructor = Vue.extend(myComp);
const comp = new Constructor({
propsData: {
someProp: 'hello world'
}
}).$mount();
console.log(document.getElementById('MyApp')); // HTMLElement {}
console.log(comp.$el) // undefined
assert(true);
});
});
});
Note that if I change the element's ID to something that doesn't exist when doing getElementById, I get the following error - which to me it would imply that it has a handle on the element when using the correct ID:
// document.getElementById('IDontExist');
[Vue warn]: Failed to mount component: template or render function not defined.
Note that my component definition is a little different since I'm going for a bare-bones approach and not using vue-cli. Examples (some code omitted for brevity):
app.vue
import myComp from './components/myComp.vue';
let app = new Vue({
el: '#MyApp',
data: {
someProp: 'hello world'
},
created: function() {},
methods: {}
});
export {app};
myComp.vue
export default Vue.component('my-comp', {
props: ['someProp'],
data: function() {},
created: {},
watch: {},
methods: {},
template: `<div>{{someProp}}</div>`
});
I have a feeling I'm overlooking something pretty simple regarding how to wire up the component mounting to the DOM, or accessing the component (but it seems to be in line with the docs). Any ideas?