Jest + Nuxt + Nuxt-Fire is failing in test suite - vue.js

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.

Related

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

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

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.

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

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)

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) => {
// ...
})
}
}