Vue jest $refs function and component mocking issue - vue.js

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.

Related

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)

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.

How do I create a dummy component to use in an Ember integration test?

I have a component some-container that takes a hash of ids mapped to other ember components. This is how it's used:
{{modules/some-container
pageKey="foo"
widgetIdToComponent=(hash
foo=(component "modules/dummy-component")
)
}}
I'm writing an integration test for this component and I want to keep the test independent of other components. Is there a way to define dummy components within an Ember integration test file?
The existing answer from #Tyler Becks is good for legacy ember-qunit, but with native qunit (since: 2017: https://github.com/emberjs/rfcs/blob/master/text/0232-simplify-qunit-testing-api.md), you'd want something like this (known to work with at least Ember Octane (3.16+)):
import { test, module } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { hbs } from 'ember-cli-htmlbars';
import { render } from '#ember/test-helpers';
import { setComponentTemplate } from '#ember/component';
import templateOnly from '#ember/component/template-only';
import Component from '#glimmer/component';
module('name of test suite', function(hooks) {
setupRenderingTest(hooks);
test('name of test', async function(assert) {
class MyComponent extends Component {}
setComponetTemplate(hbs`your template here`, MyComponent);
this.owner.register('component:component-name', MyComponent);
await render(hbs`<ComponentName />`);
});
// alternatively, things get a bit simpler if you're using
// ember-source 3.25+
test('name of test 2', async function(assert) {
class MyComponent extends Component {}
setComponetTemplate(hbs`your template here`, MyComponent);
this.setProperties({ MyComponent });
await render(hbs`<this.MyComponent />`);
});
// template-only components are even simpler still
test('name of test 3', async function(assert) {
this.setProperties({
MyComponent: setComponetTemplate(
hbs`your template here`,
templateOnly()
);
});
await render(hbs`<this.MyComponent />`);
});
});
Figured it out! The solution is to use this.register. See below:
moduleForComponent('some-container', 'Integration | Component | some-container', {
integration: true,
beforeEach() {
this.register(
'component:foo-div',
Component.extend({
layout: hbs`<div data-test-foo />`
})
);
this.register(
'component:bar-div',
Component.extend({
layout: hbs`<div data-test-bar />`
})
);
this.component = hbs`
{{modules/some-container
pageKey="foo"
widgetIdToComponent=(hash
fooId=(component "foo-div")
barId=(component "bar-div")
)
}}`;
}
});
test('it renders foo div when only foo div is returned', function(assert) {
this.render(this.component);
assert.dom('[data-test-foo]').exists('foo div renders');
assert.dom('[data-test-bar]').doesNotExist('foo div renders');
});

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