Building our component
To focus on testing, I’ve already built our component. You can copy the code for the component below or from the GitHub repository in the lesson resources.
Let’s create a new file in our components folder called NotificationToast.vue
and add the following code to it:
📁src/components/NotificationToast.vue
<template>
<div role="alert" :class="[
'notification',
status === 'error' ? 'notification--error' : null,
status === 'success' ? 'notification--success' : null,
status === 'info' ? 'notification--info' : null,
message && message.length > 0 ? 'notification--slide' : null,
]">
<p class="notification__text">
{{ message }}
</p>
<button title="close" ref="closeButton" class="notification__button"
@click="$emit('clear-notification')">
✕
</button>
</div>
</template>
<script setup>
defineProps({
status: {
type: String,
default: null
},
message: {
type: String,
default: null
}
})
</script>
<style>
.notification {
transition: all 900ms ease-out;
opacity: 0;
z-index: 300001;
transform: translateY(-100vh);
box-sizing: border-box;
padding: 10px 15px;
width: 100%;
max-width: 730px;
display: flex;
position: fixed;
top: 20px;
right: 15px;
justify-content: flex-start;
align-items: center;
border-radius: 8px;
min-height: 48px;
box-sizing: border-box;
color: #fff;
}
.notification--slide {
transform: translateY(0px);
opacity: 1;
}
.notification__text {
margin: 0;
margin-left: 17px;
margin-right: auto;
}
.notification--error {
background-color: #fdecec;
}
.notification--error .notification__text {
color: #f03d3e;
}
.notification--success {
background-color: #e1f9f2;
}
.notification--success .notification__text {
color: #146354;
}
.notification--info {
background-color: #9AC7F5;
}
.notification__button {
border: 0;
background-color: transparent;
cursor: pointer;
}
</style>
This is a simple component that can display notifications to the user. It accepts two props: status
, which specifies the type of notification (error, success, or info), and message
, which is the text to display in the notification. Our component also emits an event to clear the notification whenever the close button is clicked.
Let’s display this component in our app and confirm that it works.
Let’s display this component in our app and confirm that it works.
📁src/App.vue
<script setup>
import { ref } from "vue";
import NotificationToast from "./components/NotificationToast.vue";
const message = ref("Image uploaded successfully")
const clearNotification = () => {
message.value = ""
}
</script>
<template>
<NotificationToast
status="success"
:message=message
@clear-notification="clearNotification"/>
</template>
There may be instances where you need to test a component independently without all of its features. Vue Test Utils provides a set of utility functions and methods to mount and interact with Vue components in an isolated manner.
This dummy component is called a “stub”, and to use a stub in our tests, we’ll need access to the mount
method from Vue Test Utils, the official testing utility library for Vue.js.
Let’s install Vue Test Utils into our app.
Installation
yarn add --dev @vue/test-utils
# or
npm install --save-dev @vue/test-utils@next
We’ll also need to be able to emulate a browser environment while testing. This will give us access to some browser APIs and will allow us to test our components without interference from the actual environment (i.e an actual browser).
Vitest currently supports two packages that can help us achieve this: happy-dom and jsdom. If you open your package.json
you’ll see that we’ve had jsdom
installed for us automatically during setup.
What should we test?
Now that we have built out our component and installed the necessary dependencies, we need to decide on what features of our component we’ll be testing to ensure that our component works as intended.
Our tests need to check for the following:
- The component renders the correct style depending on the notification status.
- The notification slides up when
message
is empty. - The component emits an event when the close button is clicked.
With these goals in mind, we can start writing out the tests for this component.
Let’s create a file called NotificationToast.test.js
inside our tests folder and set it up for testing.
import { mount } from '@vue/test-utils'
import NotificationToast from '../NotificationToast.vue'
import { describe, expect, test } from 'vitest'
describe('Notification component', () => {
test('renders the correct style for error', () => {
const status = 'error'
const wrapper = mount(NotificationToast, {
props: { status }
})
expect(wrapper.classes()).toEqual(
expect.arrayContaining(['notification--error']))
})
test('renders correct style for success', () => {
const status = 'success'
const wrapper = mount(NotificationToast, {
props: { status }
})
expect(wrapper.classes()).toEqual(
expect.arrayContaining(['notification--success']))
})
test('renders correct style for info', () => {
const status = 'info'
const wrapper = mount(NotificationToast, {
props: { status }
})
expect(wrapper.classes()).toEqual(
expect.arrayContaining(['notification--info']))
})
})
Second Test: The notification fades away when message is empty.
Now that we’re done with our first set of tests, let’s get started with writing our second test.
According to our code, when users click the component’s close button, we emit our clear-notification
event that resets the message
prop to an empty string. We’re also adding or removing a notification--slide
class depending on the value of this message
prop as seen below.
test("notification slides up when message is empty", () => {
const message = "";
const wrapper = mount(NotificationToast, {
props: { message },
});
expect(wrapper.classes("notification--slide")).toBe(false);
});
Third Test: The component emits an event when the close button is clicked.
Finally, we want to test that our component emits an event whenever the close button is clicked. This is an important feature to test because we want to make sure that apart from UI features, the functionality of our component works as intended.
Let’s get started writing our third test. For this test, we’re going to use an async function because we are going to be triggering an event which returns a promise and we need to wait for this promise to resolve in order to catch the changes this event would cause.
test('emits event when close button is clicked', async () => {
const wrapper = mount(NotificationToast, {
data() {
return {
clicked: false
}
}
})
const closeButton = wrapper.find('button')
await closeButton.trigger('click')
expect(wrapper.emitted()).toHaveProperty('clear-notification')
})
We’ll also need to test that our component renders the correct message to the viewer.
test("renders correct message to viewer", () => {
const message = "Something happened, try again";
const wrapper = mount(NotificationToast, {
props: { message },
});
expect(wrapper.find("p").text()).toBe(message);
});