In this tutorial, we will learn how to unit-test conditional rendering and visibility for Components and HTML elements in Vue. We will be using Vitest as the testing framework along with Vue Test Utils.
As a prerequisite, you should have set the environment in Vite config to jsdom or happy-dom:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
},
})
You can find the code for this tutorial on GitHub.
Example Component
As an example let’s imagine we have this component in our app:
<script setup>
import { ref } from 'vue'
import Example from './Example.vue'
const isHeadlineVisible = ref(false)
const canRenderComponent = ref(true)
</script>
<template>
<div>
<h3
v-show="isHeadlineVisible"
data-test="headline"
>
I am now visible, yay!
</h3>
<button
data-test="toggle-headline"
@click="isHeadlineVisible = !isHeadlineVisible"
>
Toggle Headline
</button>
<button
data-test="toggle-component"
@click="canRenderComponent = !canRenderComponent"
>
Toggle Component
</button>
<Example v-if="canRenderComponent" />
</div>
</template>
The component contains a button that toggles the visibility of an H3
tag using v-show
. Furthermore, we have another button that toggles rendering a Vue component, Example
, using v-if
.
Each HTML element has a data-test attribute so that we can target the element specifically in our test and not rely on the HTML tag name as a selector.
Testing Visibility
To get started, we will test visibility. We will implement the test for the H3
element that asserts the outcome of using v-show
when provided a truthy or a falsy value.
First, outline the expected behavior as test cases:
import { describe } from 'vitest'
describe('App', () => {
it.todo('should hide the headline by default')
it.todo('should toggle the headline visibility when clicking on the "toggle" button')
})
Using .todo
makes Vitest
skip the test, however, it will show up in the logs as a todo, so you don’t forget about it!
Next, implement the test for the default case. By default, our boolean
ref is set to false
, hence, the headline should not be visible:
import { shallowMount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import App from './App.vue'
describe('App', () => {
it('should hide the headline by default', () => {
const wrapper = shallowMount(App, {
attachTo: document.body,
})
expect(wrapper.find('[data-test="headline"]').isVisible()).toBe(false)
})
it.todo('should toggle the headline visibility when clicking on the "toggle" button')
})
You might have gone for checking the visibility rule in the component styles to assert visibility, but there is a method in each DOMWrapper
called isVisible
that checks that for us.
Moreover, it will be truthy when the element/component opacity
is 0
, or when the visibility
CSS rule is set to hidden
.
We have also used attachTo
, which will make sure that isVisible checks the rules applied by CSS classes too. Since Jsdom doesn’t really render anything.
The default case is handled, now, write the test for the case for when the headline should be visible.
Simulate the button click by calling .trigger('click')
on the button wrapper to toggle the boolean value:
import { shallowMount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import App from './App.vue'
describe('App', () => {
it('should hide the headline by default', () => {
const wrapper = shallowMount(App)
expect(wrapper.find('[data-test-id="headline"]').isVisible()).toBe(false)
})
it('should toggle the headline visibility when clicking on the "toggle" button', async () => {
const wrapper = shallowMount(App, {
attachTo: document.body,
})
await wrapper.find('[data-test-id="toggle-headline"]').trigger('click')
expect(wrapper.find('[data-test="headline"]').isVisible()).toBe(true)
})
})
As you can notice, we are also marking this test case as async, so that we can use await inside. We need to await for the trigger
method to resolve, and the DOM to update.
Finally, since we are testing toggling, we can add the code that will hide the headline to the same test case:
import { shallowMount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import App from './App.vue'
describe('App', () => {
it('should hide the headline by default', () => {
const wrapper = shallowMount(App)
expect(wrapper.find('[data-test-id="headline"]').isVisible()).toBe(false)
})
it('should toggle the headline visibility when clicking on the "toggle" button', async () => {
const wrapper = shallowMount(App, {
attachTo: document.body,
})
await wrapper.get('[data-test="toggle-headline"]').trigger('click')
expect(wrapper.find('[data-test="headline"]').isVisible()).toBe(true)
await wrapper.get('[data-test="toggle-headline"]').trigger('click')
expect(wrapper.find('[data-test="headline"]').isVisible()).toBe(false)
})
})
When running vitest we can see that our tests are passing, yay!
Testing Conditional Rendering
Now that we have covered testing visibility, let’s test rendering! To get started, add the test cases with descriptions. They should be similar to the ones we had earlier:
it.todo('should render the Example component by default')
it.todo('should toggle rendering the Example component when clicking on the "Toggle Component" button')
First, let’s implement the test for the default case, since we are testing rendering, we have to use exists
instead of isVisible
:
it('should render the Example component by default', () => {
const wrapper = shallowMount(App)
expect(wrapper.findComponent({ name: 'Example' }).exists()).toBe(true)
})
We are also using findComponent instead of find. With components we use findComponent
, with HTML elements we use find
.
Next, implement the toggle case, similar to what we have done before, we can just test both cases of toggling:
it('should toggle rendering the Example component when clicking on the "Toggle Component" button', async () => {
const wrapper = shallowMount(App)
await wrapper.get('[data-test="toggle-component"]').trigger('click')
expect(wrapper.findComponent({ name: 'Example' }).exists()).toBe(false)
await wrapper.get('[data-test="toggle-component"]').trigger('click')
expect(wrapper.findComponent({ name: 'Example' }).exists()).toBe(true)
})
If you have Vitest still running then you should see the tests passing:
Conclusion
In conclusion:
- if you are testing rendering, use
exists
. - If we are testing visibility, use
isVisible
. - If you are selecting an HTML element use
.find
, - if you are selecting a Vue component use
findComponent
. - use
attachTo
when asserting withisVisible
.
You can find more cool and helpful unit testing tutorials here. c ya!