Component clean up fails between tests with Vue test-utils - vue.js

I have a simple component (HelloComponent) and a couple of tests. First test shallow mounts the component, prints it (wrapper of the component) on the console, and finally calls destroy() api on it. And the second test just prints it without mounting it. I was expecting the second test to print undefined but it prints the same thing (full component markup) as first test. Is my expectation incorrect ?
<!-- HelloComponent.vue -->
<template>
<div>
Hello {{name}}!
</div>
</template>
<script lang="ts">
export default {
name: 'Hello',
data() {
return {
name: ''
};
},
methods: {
setName(name) {
this.name = name;
}
}
}
</script>
import { shallowMount } from '#vue/test-utils';
import HelloComponent from '#/HelloComponent.vue';
describe('Hello component unit tests', () => {
let wrapper;
describe('Set 1', () => {
it('should load component', () => {
wrapper = shallowMount(HelloComponent, {});
expect(wrapper.exists()).toBe(true);
wrapper.vm.setName('oomer');
console.log(wrapper.html());
wrapper.destroy();
});
});
describe('Set 2', () => {
it('should log empty component', () => {
expect(wrapper.vm.name).toEqual('oomer');
console.log(wrapper.html());
});
});
});

Related

TypeError: Cannot read property 'type' of null - testing vue component with async functions

I am testing a component ComponentA.spec.js but I am getting TypeError: Cannot read property 'type' of null. It works if I get rid of the await keyword in the getData() function in the ComponentA. I am mocking the getData api call in my test but still it doesn't work.
This is the full stack
TypeError: C:[Privacy]\unknown: Cannot read property 'type' of null
at assert (node_modules/#babel/types/lib/asserts/generated/index.js:284:112)
at Object.assertIdentifier (node_modules/#babel/types/lib/asserts/generated/index.js:373:3)
at new CatchEntry (node_modules/regenerator-transform/lib/leap.js:93:5)
at Emitter.Ep.explodeStatement (node_modules/regenerator-transform/lib/emit.js:535:36)
at node_modules/regenerator-transform/lib/emit.js:323:12
at Array.forEach (<anonymous>)
at Emitter.Ep.explodeStatement (node_modules/regenerator-transform/lib/emit.js:322:22)
at Emitter.Ep.explode (node_modules/regenerator-transform/lib/emit.js:280:40)
This is Component A that i am trying to create tests for
<template>
<div class="d-flex flex-row">
<component-b />
<component-c />
</div>
</template>
<script>
import ComponentB from './ComponentB';
import ComponentC from './ComponentC';
import { getData } from 'apis';
export default {
name: 'component-a',
components: {
ComponentB,
ComponentC,
},
async created() {
await this.getData();
},
methods: {
// This function is the culprit
async getData() {
try {
const response = await getData();
} catch {
//
}
},
},
};
</script>
This is my ComponentA.spec.js file
import Vuetify from 'vuetify';
import ComponentA from 'components/ComponentA';
import { createLocalVue, shallowMount, mount } from '#vue/test-utils';
jest.mock('shared/apis', () => {
const data = require('../fixedData/data.json');
return {
getData: jest.fn().mockResolvedValue(data),
};
});
const localVue = createLocalVue();
let vuetify;
function createShallowWrapper(options = {}) {
return shallowMount(ComponentA, {
localVue,
vuetify,
...options,
});
}
beforeEach(() => {
vuetify = new Vuetify();
});
describe('ComponentA', () => {
describe('component creation', () => {
test('testing', () => {
const wrapper = createShallowWrapper();
expect(wrapper).toMatchSnapshot();
});
});
});
Adding exception variable (e) to my catch in the getData function in ComponentA fixed it.

Vuex + Jest + Composition API: How to check if an action has been called

I am working on a project built on Vue3 and composition API and writing test cases.
The component I want to test is like below.
Home.vue
<template>
<div>
<Child #onChangeValue="onChangeValue" />
</div>
</template>
<script lang="ts>
...
const onChangeValue = (value: string) => {
store.dispatch("changeValueAction", {
value: value,
});
};
</scirpt>
Now I want to test if changeValueAction has been called.
Home.spec.ts
...
import { key, store } from '#/store';
describe("Test Home component", () => {
const wrapper = mount(Home, {
global: {
plugins: [[store, key]],
},
});
it("Test onChangeValue", () => {
const child = wrapper.findComponent(Child);
child.vm.$emit("onChangeValue", "Hello, world");
// I want to check changeValueAction has been called.
expect(wrapper.vm.store.state.moduleA.value).toBe("Hello, world");
});
});
I can confirm the state has actually been updated successfully in the test case above but I am wondering how I can mock action and check if it has been called.
How can I do it?
I have sort of a similar setup.
I don't want to test the actual store just that the method within the component is calling dispatch with a certain value.
This is what I've done.
favorite.spec.ts
import {key} from '#/store';
let storeMock: any;
beforeEach(async () => {
storeMock = createStore({});
});
test(`Should remove favorite`, async () => {
const wrapper = mount(Component, {
propsData: {
item: mockItemObj
},
global: {
plugins: [[storeMock, key]],
}
});
const spyDispatch = jest.spyOn(storeMock, 'dispatch').mockImplementation();
await wrapper.find('.remove-favorite-item').trigger('click');
expect(spyDispatch).toHaveBeenCalledTimes(1);
expect(spyDispatch).toHaveBeenCalledWith("favoritesState/deleteFavorite", favoriteId);
});
This is the Component method:
setup(props) {
const store = useStore();
function removeFavorite() {
store.dispatch("favoritesState/deleteFavorite", favoriteId);
}
return {
removeFavorite
}
}
Hope this will help you further :)

Jest + Coverage + VueJs how to cover vue methods?

I was trying to cover the codes to increase the coverage
report percentage,
How to cover the if statement inside vue methods?
In my case using #vue/test-utils:"^1.1.4" and vue: "^2.6.12" package version, FYI, And below is my actual component,
<template>
<div :class="iconcls" >
<el-image
ref='cal-modal'
class="icons"
#click="handleRedirectRouter(urlname)"
:src="require(`#/assets/designsystem/home/${iconurl}`)"
fit="fill" />
<div class="desc" >{{ icondesc }}</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
#Component({
components: {}
})
class IconHolder extends Vue {
#Prop({ default: "" }) iconcls!: any;
#Prop({ default: "" }) iconurl!: any;
#Prop({ default: "" }) icondesc!: any;
#Prop({ default: "" }) urlname!: any;
handleRedirectRouter(url: string) {
if (url !== "") {
this.$router.push({ name: url });
}
}
}
export default IconHolder;
</script>
Coverage Report for Iconholder.vue component
EDIT 2 : Ater #tony updation,
i have tried with this below test suties but still getting errors,
import Vue from "vue";
import Vuex from "vuex";
import IconHolder from '#/components/designsystem/Home/IconHolder.vue';
import ElementUI, { Image } from "element-ui";
import { mount, createLocalVue } from '#vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(ElementUI, {
Image
});
Vue.component('el-image', Image);
describe("IconHolder.spec.vue", () => {
it('pushes route by name', async () => {
const push = jest.fn();
const wrapper = mount(IconHolder, {
propsData: {
iconcls:"dshomesec5_comp_icons",
icondesc:"about",
iconurl:"components_icn_15.svg",
urlname: 'about'
},
mocks: {
$router: {
push
}
}
})
await wrapper.findComponent({ name: 'el-image' }).trigger('click');
expect(push).toHaveBeenCalledWith({ name: 'about' });
})
})
ERROR REPORT:
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: {"name": "about"}
Number of calls: 0
30 | })
31 | await wrapper.findComponent({ name: 'el-image' }).trigger('click');
> 32 | expect(push).toHaveBeenCalledWith({ name: 'about' })
Create a unit test that runs that method with a non-empty string for url.
Mount the component with an initial non-empty urlname prop.
Mock the $router.push method with jest.fn(), which we'll use to verify the call later.
Find the el-image component that is bound to that method (as a click handler).
Trigger the click event on that component.
Verify $router.push was called with the specified urlname.
it('pushes route by name', () => {
/* 2 */
const push = jest.fn()
const wrapper = shallowMount(IconHolder, {
/* 1 */
propsData: {
urlname: 'about'
},
/* 2 */
mocks: {
$router: {
push
}
}
})
/* 3 👇*/ /* 4 👇*/
await wrapper.findComponent({ name: 'el-image' }).trigger('click')
/* 5 */
expect(push).toHaveBeenCalledWith({ name: 'about' })
})

How to mock VueAxios in jest

I want to test my Api functions which are on separate file outside vue component. Inside this methods i call api by Vue.axios, and i can't find the way to mock and test it like in this post:
How do I test axios in jest
example method:
cancelAuction: function (auction_id) {
if (validateApiInt(auction_id)) {
return Vue.axios.delete(`/auctions/${auction_id}`);
}
return {};
},
example usage:
const response = await AuctionApi.cancelAuction(id);
Ok that was pretty obvious. I had to mock whole Vue like below:
jest.mock('vue', () => ({
axios: {
get: jest.fn()
},
}));
Just start learning Jest + #vue/test-utils. Here is a simple example for those people want to mock "vue-axios".
// #/components/Helloword.vue
<template>
<div>
<h1>Email: <span>{{ email }}</span></h1>
<button #click="fetchData">Get Random Email</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
email: '',
};
},
methods: {
async fetchData() {
const res = (await this.axios.get('https://randomuser.me/api/')).data
.results[0].email;
this.email = res;
},
},
};
</script>
--
// test/unit/example.spec.js
import { mount } from '#vue/test-utils';
import HelloWorld from '#/components/HelloWorld.vue';
import axios from 'axios';
jest.mock('axios', () => ({
get: () =>
Promise.resolve({
data: {
results: [{ email: 'mockAxios#email.com' }],
},
}),
}));
describe('HelloWorld.vue', () => {
it('click and fetch data...', async (done) => {
const wrapper = mount(HelloWorld, {
mocks: {
axios,
},
});
await wrapper.find('button').trigger('click');
wrapper.vm.$nextTick(() => {
expect(wrapper.find('h1').text()).toContain('#');
done();
});
});
});

Quasar Unknown custom element error in unit test

I have a simple Vue component that uses Quasar button
<template>
<div>
<span class="count">{{ count }}</span>
<q-btn #click="increment">Increment</q-btn>
</div>
</template>
<script>
export default {
name: 'TestComponent',
data() {
return {
count: 0,
};
},
methods: {
increment() {
this.count += 1;
},
},
};
</script>
I create a unit test for it
import { mount, createLocalVue } from '#vue/test-utils';
import { Quasar, QBtn } from 'quasar';
import TestComponent from '../TestComponent';
describe('TestComponent', () => {
let wrapper;
beforeEach(() => {
const localVue = createLocalVue();
localVue.use(Quasar, { components: { QBtn } });
wrapper = mount(TestComponent, { localVue });
});
it('renders the correct markup', () => {
expect(wrapper.html()).toContain('<span class="count">0</span>');
});
// it's also easy to check for the existence of elements
it('has a button', () => {
expect(wrapper.contains('button')).toBe(true);
});
});
My problem:
If I run the test cases (it function) one by one at a time the test will pass. For example, remove the second it('has a button'...) then run the test. It'll pass. It's the same when removing the first it('renders the correct markup'...)
However, If I keep all test cases then run the test. The second test case will fail with an error
console.error node_modules/vue/dist/vue.common.dev.js:630
[Vue warn]: Unknown custom element: <q-btn> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
found in
---> <TestComponent>
<Root>
What am I doing wrong?
Try removing the before-each. I saw this problem too. Can't remember what exactly fixed it but this is how I have my describe block.
describe('Mount Quasar', () => {
const localVue = createLocalVue()
localVue.use(Quasar, { components })
const wrapper = shallowMount(Register, {
localVue,
stubs: ['router-link', 'router-view']
})
const vm = wrapper.vm
it('passes the sanity check and creates a wrapper', () => {
expect(wrapper.isVueInstance()).toBe(true)
})
})
You will need to import quasar into either webpack, babel, or jest.
In the jest.config.js file
Add
moduleNameMapper: {
quasar: "quasar-framework/dist/umd/quasar.mat.umd.min.js"
},