Testing with `Created` hook with `vue-test-utils` and `jest` - vue.js

I have a Vue page like this:
<template>
</template>
<script>
created(){
this.doSomething();
}
methods: {
doSomething() {
.....
}
}
</script>
Now, we want to the testing of this created hook and check that doSomething() method is called.
Tried like this, jest is also imported in package.json
import {
shallowMount,
createLocalVue,
} from '#vue/test-utils';
const localVue = createLocalVue();
import Xyx from '/Xyx.vue';
const init = () => {
wrapper = shallowMount(Xyx, { localVue });
cmp = wrapper.vm;
};
describe('#created', () => {
it('#doSomething', () => {
init();
wrapper.setMethods({
doSomething: jest.fn(),
})
expect(cmp.doSomething).toHaveBeenCalled();
});
});
Can I do the unit test case of this created hook?

The methods option was deprecated in v1 of #vue/test-utils, so the accepted answer no longer works. I ran into this issue myself and decided to dig into the source to figure out how I could test this.
It looks like Vue actually stores all the hooks in the $options property. Each hook has an option that is an array of functions. It is important to note that a context is not bound to said functions, so you will need to use call or apply to execute them.
vm.$options.created.forEach(hook => {
hook.call(vm);
});

Because your method is called on created, it is run before you are setting the mock. Therefore, your test will fail.
You have to replace the method with the mock on initialization (in your case, on shallowMount):
describe('Xyz', () => {
it('should call doSomething() when created', () => {
const doSomething = jest.fn()
wrapper = shallowMount(Xyz, {
localvue,
methods: { doSomething }
});
expect(doSomething).toHaveBeenCalled();
});
});
Sidenote: you're not declaring cmp. At the start of your test, you should have a let cmp;
A very similar discussion here. Above the linked comment there's a method to mock properties of most Vue component lifecycle hooks.

It's possible to call hooks when we need in our tests. For example if we need to mock some data before calling a hook.
import App from '#/App.vue';
// in test
App.created.call(wrapper.vm);
Also in Typescript if we use vue-property-decorator it changes the shape of component, so needs to be done like this:
App.extendOptions.created.call(wrapper.vm)

Related

testing method calls in `mounted` lifecycle hook in vue test utils now that `methods` is deprecated and will be removed in next major version

I have the following lifecycle hook in my component:
async mounted () {
await this.fetchTabData()
},
said method calls a decoupled method as the data it requests can be refreshed based on user activity at runtime (ie. switching between "tabs" that each call async data)
In order to get test coverage for the above I wrote the following:
describe('mounted', () => {
test('test', async () => {
const fetchTabData = jest.fn()
wrapper = await shallowMount(Overview, {
store: new Vuex.Store({ ... }),
...
methods: { fetchTabData }
})
expect(fetchTabData).toHaveBeenCalled()
})
})
VTU tells me
[vue-test-utils]: overwriting methods via the methods property is deprecated and will be removed in the next major version. There is no clear migration path for the methods property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex method extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
what is the proposed solution therefore (if there is one/are any), when the given complex method is itself a lifecycle hook?
the below passes my test and the warning no longer appears
[Edit: to clarify, Overview is the component to be tested]
describe('mounted', () => {
test('test', async () => {
const fetchTabData = jest.fn()
Overview.methods.fetchTabData = fetchTabData
wrapper = await shallowMount(Overview, {
store: new Vuex.Store({ ... }),
...
})
expect(fetchTabData).toHaveBeenCalled()
})
})
the same approach should, in theory, work for other instances where the lifecycle hook is calling methods.

How Test with Jest a function in the method "mounted" VueJS

I would to try call a function already mocked. I use vueJS for the frond and Jest as unit test. Below a example of my code. My purpose is to test the call of « anotherFunction". The first test is succeed , not the second.Thanks for help or suggestion
code vueJS:
mounted() {
this.myfunction();
}
methods: {
myfunction() {
this.anotherFunction();
}
}
Jest code:
describe('Home.vue', () => {
let wrapper = null;
const options = {
mocks: {
$t: () => 'some specific text',
},
methods: {
myFunction: jest.fn(),
},
};
it('Should renders Home Component', () => {
// Given
wrapper = shallowMount(Home, options);
// Then
expect(wrapper).toBeTruthy();
});
it('Should call anotherFunction', async (done) => {
// Given
wrapper.vm.anotherFunction = jest.fn().mockResolvedValue([]);
// When
await wrapper.vm.myFunction();
// THIS THE PROBLEM, myFunction is mocked and I can't call the function 'anotherFunction' inside...
// Then
// expect(wrapper.vm.anotherFunction).toHaveBeenCalled();
});
});
I was finding a good way to help you if this test case. So, I thought in something like the chuck code below:
import { mount } from '#vue/test-utils';
describe('Home', () => {
it('method calls test case', () => {
const anotherMethodMock = jest.fn();
wrapper = mount(Home, {
methods: {
anotherMethod: anotherMethodMock
}
});
expect(anotherMethodMock).toHaveBeenCalled();
});
});
But, the Jest threw the following exception:
[vue-test-utils]: overwriting methods via the methods property is deprecated and will be removed in the next major version. There is no clear migration path for themethods property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex m ethod extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
I had the following insight, maybe, in this case, should be better to test the side effect of this anotherMethod calling. What does it change? Is something being shown to the user?
I believe that here we have started from the wrong concept.
I hope that this tip could be useful :)
As suggested by #Vinícius Alonso, We should avoid using methods and setMethods in our test cases because of it's deprecation. But you can still test the mounted lifecycle by mocking the functions that are being called during mount. So you can do something similar to below snippet.
describe('Mounted Lifecycle', () => {
const mockMethodOne = jest.spyOn(MyComponent.methods, 'methodOne');
const mockMethodTwo = jest.spyOn(MyComponent.methods, 'methodTwo');
it('Validate data and function call during mount', () => {
const wrapper = shallowMount(MyComponent);
expect(mockMethodOne).toHaveBeenCalled();
expect(mockMethodTwo).toHaveBeenCalled();
})
})
Do mount/shallowMount inside it only rather putting it outside of it as it was not working in my case. You can checkout more details on it if you want.

Passing mocked methods to mount/shallowMount using Vue Test Utils

Can somebody please explain to me why a mocked function passed in a methods object to shallowMount can not be accessed in the test through the wrapper object and instead must be accessed by first creating a variable as a reference to the mocked function?
I have tried mount and shallowMount, created/mounted hooks and also by calling the function directly as opposed to inside the created/mounted hook.
// TestComponent.spec.js
import TestComponent from '#/components/TestComponent'
import { shallowMount, createLocalVue } from '#vue/test-utils'
const localVue = createLocalVue()
const setLoadingMock = jest.fn() // mock function that is accessible in the test
function createWrapper () {
const defaultMountingOptions = {
localVue,
methods: {
setLoading: setLoadingMock
}
}
return shallowMount(TestComponent, defaultMountingOptions)
}
describe('TestComponent.vue', () => {
let wrapper
beforeEach(() => {
wrapper = createWrapper()
});
it('will call setLoading', () => {
expect(wrapper.vm.setLoading).toHaveBeenCalled()
// FAILS. Console message:
// Matcher error: received value must be a mock or spy function
// Received has type: function
// Received has value: [Function bound mockConstructor]
})
it('will call setLoading', () => {
expect(setLoadingMock).toHaveBeenCalled() // PASSES
})
})
TestComponent.vue
export default {
name: 'TestComponent',
mounted () {
this.setLoading()
},
methods: {
setLoading () {
console.log('Original method'); // Never logs
}
}
}
mount or shallowMount are not important in this case. mount means test will mount component and its child components, while shallowMount will mount only component and stub its child components.
You are mocking the setLoading method, which means that you are replacing the original method with a mock. Meaning, when setLoading method is called, it won't run the code from your component, but code from the test mock - in this case jest.fn().
Purpose of mocking is to check if the mocked method was called correctly.
Also, wrapper.vm.setLoading calls the setLoading method.
Instead of referencing the wrapper instance, you should spy the method, e.g.:
const setLoading = jest.spyOn(wrapper.vm, 'setLoading');
expect(setLoading).toHaveBeenCalled() ;

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 Adding to lifecycle hooks on every component

So I have a loader screen in my app, and the idea is to show the loader screen on the beforeCreate hook so the user can't see the stuff being rendered, and then on the mounted hook remove the loader screen.
This is fun and nice for when you have two or three view/components, but currently my app has a lot more than that, and adding it to each component/view doesn't make much sense for me.
So I was wondering, is there any way to add something to the beforeCreate and mounted hooks on a global scope. Something like this:
main.js
Vue.beforeCreate(() => {
//Show the loader screen
});
Vue.mounted(() => {
//Hide the loader screen
});
That way it would be applied to every component and view
You can use mixins for this purposes, and import in components.
//mixins.js
export default {
beforeCreate() {},
mounted() {}
}
And in component add mixins: [importedMixins]
You will have access to 'this'.
Actualy you can use and vuex to (mapGetters, mapActions etc.)
If you don't want include mixins in every component, try to use vue plugins system (https://v2.vuejs.org/v2/guide/plugins.html):
MyPlugin.install = function (Vue, options) {
// 1. add global method or property
Vue.myGlobalMethod = function () {
// something logic ...
}
// 2. add a global asset
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// something logic ...
}
...
})
// 3. inject some component options
Vue.mixin({
created: function () {
// something logic ...
}
...
})
// 4. add an instance method
Vue.prototype.$myMethod = function (methodOptions) {
// something logic ...
}
}
And use your plugin like this Vue.use(MyPlugin, { someOption: true })
There is something very silimar to your request in vue-router. I've never used afterEach but beforeEach works perfectly.
router.beforeEach((to, from, next) => {
/* must call `next` */
})
router.beforeResolve((to, from, next) => {
/* must call `next` */
})
router.afterEach((to, from) => {})
Here is a documentation
There is also a hook called 'beforeRouteEnter'.
Link to beforeRouteEnter docs