Heya!
Before I start, I'll share my short background on why this release means a lot to me :) Feel free to skip to the detail section below (More Reliable)
As part of the Frontend team in Piktochart, we have been using VueJS since 2 years ago and it was a blast to migrate from native JS to a framework that helps with code organizing and better practices. Naturally, this comes along with the automated test too!
We have been using Jest and VueTestUtils since the first time we migrate to Vue, and we rely on it to test the Vue components. We've tried our best to follow the recommended tips, which preferably uses shallowMount
, simulate the component's data input (ex: props, network response, user interaction) and assert the output (ex: rendered HTML, emitted events).
Yet, sometimes we faces issue in the testing process and we need to have a workaround to ensure the test can simulate and output the test scenario. We understand that it's because the VueTestUtils has been in a beta
state since late 2017, so it's still prone to bugs and API changes. Let me know by commenting below if you face some similar issues.
Which is why, when they finally release the stable version, we can be sure that the test is going to be better.
Let's go through the release in detail below! :D
More Reliable
The stable release has tried its best to fix the known problems of the test framework when it's in the beta
phase. Here are some of the bugs that I've faced before, and fixed in the latest release:
Fix on shallowMount
, cannot test child component's slot
Link to the reported issue: 1, 2, 3
When a component uses the child component's slot to render the content, we need to test the interactivity of the content from the slot as well. It was working fine if the slot is a default slot, but it isn't for named slot. Take a look at this example (using b-modal from bootstrap-vue):
// parent component
<template>
<b-modal v-model="visible" static>
<template v-slot:modal-header>
Header Title
</template>
Modal Content
</b-modal>
</template>
note: the b-modal has static props, so the modal is rendered inside the parent component
We would like to test if the modal header renders the correct title:
const wrapper = shallowMount(parentComponent);
const modalWrapper = wrapper.find({ name: 'BModal' });
expect(wrapper.html()).toContain('Header Title');
In the test, the rendered HTML when the visible: true
is:
// wrapper.html()
<b-modal-stub size="md" ignoreenforcefocusselector="" title="" titletag="h5" headerclosecontent="&times;" headercloselabel="Close" canceltitle="Cancel" oktitle="OK" cancelvariant="secondary" okvariant="primary" static="true" visible="true">
Modal Content
</b-modal-stub>
Notice that the default slot ("Modal Content") is rendered, but the named slot header ("Header Title") is missing 😧. Because of this, we cannot test the content rendered in the header.
The workaround is to stub the child component to the real component, so it'll render the default and named slot.
import { BModal } from 'bootstrap-vue'
const wrapper = shallowMount(parentComponent, {
stubs: {
BModal
}
});
This test has been failing in the < v1.0.0-beta.27
, and finally passed in the v1.0.0!
Although it might not be a proper fix, the Core team is looking into it and they're looking for help too if you are interested 😉.
Fix on shallowMount
, cannot assert child component's v-model
This is the reported issue: 1
When a component that binds the data to the child component using v-model
, it wasn't testable before because the props aren't detected.
This is the test example:
// parent component
<template>
<input-link v-model="url" />
</template>
<script>
export default {
data () {
return { url: '' }
}
}
</script>
To ensure the parent component has passed the right data to the child component (<input-link>
), we need to test it:
it('gets data binding of `url` to <input-link> component', async () => {
const newUrl = 'https://chenxeed.com';
const wrapper = shallowMount(HelloWorld);
await wrapper.setData({ url: newUrl }); // await to change the child props
const inputLink = wrapper.find({ name: 'InputLink' });
expect(inputLink.props('url')).toBe(newUrl);
})
This test has been failing in the v1.0.0-beta.25
, and passed in the v1.0.0
!
Fix on shallowMount
, cannot assert child component loaded by dynamic import
This is the reported issue: 1, 2
When a component that loads the child component by using dynamic import, it wasn't testable before because the child aren't rendered properly.
This is the test example:
<template>
<child-component/>
</template>
<script>
export default {
components: {
ChildComponent: () => import('./child-component')
}
}
</script>
We need to assert if the child component is loaded:
const wrapper = shallowMount(parentComponent);
const childComponent = wrapper.find({ name: 'ChildComponent' });
expect(childComponent.exists()).toBeTruthy();
This test is not working, and there's a fix on v1.0.0-beta.28
to render the dynamic import component in shallowMount
but you have to run nextTick
first. The quirk is, the rendered component is not stubbed 😳.
const wrapper = shallowMount(parentComponent);
await wrapper.vm.$nextTick(); // must await nextTick to render the child
const childComponent = wrapper.find({ name: 'ChildComponent' });
expect(childComponent.exists()).toBeTruthy();
Although it might not be a proper fix, the Core team is looking into it and they're looking for help too if you are interested 😉.
There's more changes that I can't cover all here, so apologies if I missed to highlight any issues that you have faced before 🙇 You can check the releases for the fixes they have made.
Now let's highlight the better practice introduced in the stable release! 💖
Better testing practice
The stable release has introduced a better practice on handling user event like mouse click, and any events that changes the component data.
Previously, we need to manually run a nextTick
whenever we trigger the event or changing some data:
wrapper.setData({
url: 'newurl'
});
await wrapper.vm.$nextTick();
expect(wrapper.html()).toContain('newurl');
Now, we can shortcut this by awaiting the setter function:
await wrapper.setData({
url: 'newurl'
});
expect(wrapper.html()).toContain('newurl');
Isn't it lovely? 😍 Same goes for the event trigger like click:
await wrapper.trigger('click');
expect(wrapper.emitted().clicked).toBeTruthy();
Deprecation warning
The stable release also introduce some warnings to deprecate the usage of some API that they find not necessary, or can be replaced. Here are the APIs to be deprecated:
-
attachToDocument
is deprecated and will be removed in future releases. UseattachTo
instead. -
isEmpty
is deprecated and will be removed in future releases. -
isVueInstance
is deprecated and will be removed in future releases. -
setMethods
is deprecated and will be removed in future releases. -
emittedByOrder
is deprecated and will be removed in future releases. - Using
find
to search for a Component is deprecated and will be removed. UsefindComponent
instead. - Using
findAll
to search for Components is deprecated and will be removed. UsefindAllComponents
instead. -
isVisible
is deprecated and will be removed in future releases. -
isVueInstance
is deprecated and will be removed in future releases. -
name
is deprecated and will be removed in future releases. -
overview
is deprecated and will be removed in future releases.
note: you can disable the warnings if it annoys your testing. Check the release log for more detail
These API are likely to be removed on the next version for the Vue 3 support, so keep in mind as it'll likely break your existing tests if you are migrating your Vue 2 app to Vue 3!
Summary
The VueJS Developer has been going through some roller coaster on testing their components with the issues reported, and this release is a green light to let the developer be more confident on writing the unit tests.
Really appreciate the effort of the VueTestUtils Core team to release the stable version, while preparing for the upcoming Vue 3 test 😍
I hope this article helps you to look forward on the Vue Test Utils upgrade, and let me know in the comment if anything is unclear. Enjoy testing!
Top comments (0)