Passing mocked methods to mount/shallowMount using Vue Test Utils - vue.js

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

Related

Vue test utils: mock getter return value

I'm writing a test that mocks the return value of a namespaced getter, which accepts a single argument. The test parameter will be the mocked return value for the getter, which controls the visibility of an element on the page.
Mocking the getter with a generic return value allows it to be defined, but so far mocking the return value with the test parameter is not working.
Getter (in the "app" module):
const getters = {
myGetter:
(state) =>
(getterArg) => {
return state.someStateVariable === true
? valueForTrue
: valueForFalse;
}
};
Test:
import { cloneDeep } from "lodash";
import { createLocalVue, shallowMount } from "#vue/test-utils";
import MyComponent from "#/views/MyComponent.vue";
import { storeConfig } from "#/store";
import Vuex from "vuex";
describe("My test", () => {
let localVue;
let store;
let wrapper;
beforeEach(() => {
localVue = createLocalVue();
localVue.use(Vuex);
});
test.each([[false], [true]])(
"Test param: '%s'",
(testParam) => {
store = new Vuex.Store({
modules: {
app: {
namespaced: true,
...cloneDeep(storeConfig.modules.app),
state: {
...cloneDeep(storeConfig.modules.app.state),
getters: {
...cloneDeep(storeConfig.modules.app.getters),
// getter defined, but doesn't include test param
myGetter: jest.fn().mockReturnValue(jest.fn())
// getter variation with a mocked return value (test fails due to testParam not being returned)
myGetter: jest.fn().mockReturnValue(jest.fn().mockReturnValue(testParam))
// getter variation with the argument passed by the component, and a mocked return value (test fails due to testParam not being returned)
myGetter: jest.fn("getterArg").mockReturnValue(jest.fn().mockReturnValue(testParam))
}
}
}
});
wrapper = shallowMount(MyComponent, {
localVue,
store
});
expect(wrapper.find("#my-element").exists()).toBe(
testParam
);
I also tried updating the wrapper after creation:
// Test fails due to getter being undefined
Object.defineProperty(wrapper.vm, "app/myGetter", {
value: () => {
return testParam;
}
});
// Test fails due to getter being undefined
Object.defineProperty(wrapper.vm, "app/myGetter", {
value: ("getterArg") => {
return testParam;
}
});
How can this namespaced getter be configured to dynamically return the test parameter?

Vue jest $refs function and component mocking issue

I'm using a node module called headroom for one of my vue components. I'm trying to mock the library and write the test but i'm getting a this.$refs.headroom._setHeightOffset is not a function it seems like the function in the $ref isn't defined.
Vue Component
<script>
import { headroom } from 'vue-headroom';
name: 'Test',
components: {
headroom
},
mounted() {
this.$refs.headroom._setHeightOffset();
}
</script>
<template>
<headroom ref="headroom">
<OtherComponent />
<headroom>
</template>
In my test file I'm doing a shallowMount and mocking the imported library like below but it still isn't picking up the function in my test.
jest.mock('vue-headroom', () => ({
name: 'headroom',
headroom: {
_setHeightOffset: jest.fn(),
},
}));
I tried mocking it like this as well but still didn't work.
wrapper.vm.$refs.headroom._setHeightOffset = jest.fn();
You need to use mount if you want to try to mock internal method _setHeightOffset.
Or you can use shallow mount and then create stub on headroom.
Why? Because headroom there is child component of your custom Test Vue Component. which headroom has been stub automatically if you use shallowMount. Reference: shallowMount.
I created several example on how to spy or mock it. You can choose which one suitable for you.
// File: ComponentTest.spec.js
import { headroom } from 'vue-headroom';
import { mount, shallowMount } from '#vue/test-utils';
import Component from '#/components/ComponentTest.vue';
describe('ComponentTest.vue', () => {
it('example spy & mount', () => {
const spy = jest.spyOn(headroom.methods, '_setHeightOffset');
const wrapper = mount(Component);
expect(wrapper.vm).toBeTruthy();
expect(spy).toHaveBeenCalledTimes(2);
});
it('example mock & mount', () => {
const mock = jest.fn();
headroom.methods._setHeightOffset = mock;
const wrapper = mount(Component);
expect(wrapper.vm).toBeTruthy();
expect(mock).toHaveBeenCalledTimes(2);
});
it('example mock & shallow mount', () => {
const mock = jest.fn();
headroom.methods._setHeightOffset = mock;
const wrapper = shallowMount(Component, {
stubs: {
headroom,
},
});
expect(wrapper.vm).toBeTruthy();
expect(mock).toHaveBeenCalledTimes(2);
});
});
Example Test Component (simplified)
// File ComponentTest.vue
<script>
import { headroom } from 'vue-headroom';
export default {
name: 'Test',
components: { headroom },
mounted() {
this.$refs.headroom._setHeightOffset();
},
};
</script>
<template>
<headroom ref="headroom">
xxx
</headroom>
</template>
Then I run jest to run ComponentTest.spec.js
$ npx jest ComponentTest.spec.js
PASS test/ComponentTest.spec.js
ComponentTest.vue
✓ example spy & mount (12 ms)
✓ example mock & mount (2 ms)
✓ example mock & shallow mount (2 ms)
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 2.176 s
Note:
I use toHaveBeenCalledTimes(2). Why? because the internal method has been called inside headroom.vue, and you try to call it again.

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.

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

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)

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.