Vuejs testing with Mocha: component not mounting to element - testing

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?

Related

How to test #InjectReactive() of Vue-Property-Decorator on vue-test-utils?

I am not sure how to provide the inject-reactive data when I mount the component. I don't need the exact solution, just a hint would be much appreciated.
Here is my parent component:
#Component
export default class Preferences extends Vue {
#ProvideReactive() serviceLevels: CarrierFilterType[] = [];
// more code here...
Here is my child component:
#Component
export default class CarrierFilters {
#InjectReactive() readonly serviceLevels!: CarrierFilterType[];
// more code here...
Here is my test file:
// other imports are here...
import { supportedServiceLevels } from '../../constant';
// initial setup code here...
describe('Settings > Preferences > Content > CarrierFilters.vue', () => {
beforeEach(() => {
options = {
localVue,
i18n,
store,
router,
vuetify: new Vuetify(),
provide: { // I am not sure how to provide the inject-reactive data here...?
serviceLevels: [...supportedServiceLevels],
},
};
initWrapper = shallowMount(CarrierFilters, options);
});
// more code here...
Currently, I am getting the below console error when I run the test:
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: Injection "__reactiveInject__" not found
found in
---> <CarrierFilters>
<Root>
By the way, the above code is working with the #Inject but not with the #InjectReactive. I have seen their source code, seems like I have to provide this key __reactiveInject__ somehow.
Put all reactive properties inside __reactiveInject__. For example:
const wrapper = mount(Component, {
localVue,
provide: {
__reactiveInject__: {
foo: "bar"
},
},
});

Vue Test Utils - Skip created hook

I want to skip all of the methods that are being called within the created() hook. Is there a way to do this?
So instead of this
created() {
this.getAllocations();
this.getModels();
this.getTeams();
this.getCustodians();
this.getDefaultFeeStructure();
}
I want this
created() { }
It's worth noting, I cannot actually change the component itself, but for testing purposes, this needs to be done.
You can accomplish this with a global mixin (see https://v2.vuejs.org/v2/guide/mixins.html#Global-Mixin)
However, for your case you need a custom merge strategy to prevent the created hook on the component from being run:
Hook functions with the same name are merged into an array so that all of them will be called. Mixin hooks will be called before the component’s own hooks. (https://v2.vuejs.org/v2/guide/mixins.html#Option-Merging)
See a working example at https://jsfiddle.net/rushimusmaximus/9akf641z/3/
Vue.mixin({
created() {
console.log("created() in global mixin")
}
});
const mergeCreatedStrategy = Vue.config.optionMergeStrategies.created;
Vue.config.optionMergeStrategies.created = (parent, child) => {
return mergeCreatedStrategy(parent);
};
new Vue ({
el: "#vue-app",
template: '<p>See console output for logging. Rendered at {{renderDate}}</p>',
data() {
return {
renderDate: new Date()
}
},
created() {
console.log("created() in component")
}
})

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)
}

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)
});

Testing Methods within Vue Components using Jasmine

I have the following test which works great
it('does not render chapter div or error div', () => {
const payLoad = chapter;
const switcher = 'guild';
var vm = getComponent(payLoad, switcher).$mount();
expect(vm.$el.querySelector('#chapter-card')).toBeNull();
expect(vm.$el.querySelector('#error-card')).toBeNull();
});
To do this I wrote a helper method that mounts a component:
const getComponent = (prop1) => {
let vm = new Vue({
template: '<div><compd :payLoad="group" :index="index" "></compd ></div></div>',
components: {
compd,
},
data: {
payLoad: prop1,
},
})
return vm;
}
however, I have a method within my vue component compd. For simplicitys sake, lets call it
add(num,num){
return num+num;
}
I want to be able to write a test case similar to the following:
it('checks the add method works', () => {
expect(compd.add(1,2).toBe(3));
});
I cannot figure out how to do this. Has anyone any suggestions?
The documentation here:
https://v2.vuejs.org/v2/guide/unit-testing.html
Does not cover testing methods.
Source code from vue repo
As you can see the method gets called simply on the instance
const vm = new Vue({
data: {
a: 1
},
methods: {
plus () {
this.a++
}
}
})
vm.plus()
expect(vm.a).toBe(2)
You can also access the method via $options like in this case (vue source code)
const A = Vue.extend({
methods: {
a () {}
}
})
const vm = new A({
methods: {
b () {}
}
})
expect(typeof vm.$options.methods.a).toBe('function')
Update:
To test child components use $children to access the necessary child. Example
var childToTest = vm.$children.find((comp)=>comp.$options.name === 'accordion')` assuming name is set to `accordion`
After that you can
childToTest.plus();
vm.$nextTick(()=>{
expect(childToTest.someData).toBe(someValue)
done(); //call test done callback here
})
If you have a single child component and not a v-for put a ref on it
`
vm.$refs.mycomponent.myMethod()