A new series is out: Vue2 and Vue3 Tips Booklet
Wechat search [Great Relocation to the World], I will share with you the front-end industry trends, learning methods, etc. as soon as possible.
This article GitHub https://github.com/qq449245884/xiaozhi has been included, there are complete test sites, materials and my series of articles for interviews with first-line manufacturers.
What is Vitest?
Since Vite 's build tool, Vite, gained huge popularity, there is now an extremely fast unit testing framework powered by it. Vitest .
Vitest is compatible with Jest, has ESM, Typescript and JSX support out of the box, and is powered by esbuild . It uses the Vite development server to convert your files during testing and listens for the same configuration of your app (via vite.config.js
), eliminating the duplication of work involved with testing alternatives like Jest.
Why choose Vitest?
Vite is a build tool designed to provide a faster and leaner development experience for modern web projects. It supports common web patterns, glob
imports and SSR out of the box. Its many plugins and integrations are promoting a vibrant ecosystem.
But this leads to a new problem: how to write unit tests on Vite.
Using frameworks like Jest with Vite resulted in a lot of duplicate configuration between Vite and Jest, and Vitest solves this problem by eliminating the extra configuration required to write unit tests for our application. Vitest uses the same configuration as Vite and shares a common transformation pipeline for development, build and test. It can also be extended using the same plugin API as Vite and is compatible with Jest's API to facilitate migration from Jest without much refactoring.
Therefore, Vitest is also very fast.
How to use Vitest to test components
Install Vitest
Using Vitest
in a project requires Vite >=v2.7.10
and Node >=v14
to work.
You can use npm
, yarn
or pnpm
to install Vitest, depending on your preference, run the following command in the terminal:
npm
npm install -D vitest
YARN
yarn add -D vitest
PNPM
pnpm add -D vitest
Vitest configuration
After installing Vitest, you need to add this to the vite.config.js
file:
vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [vue()],
//add test to vite config
test: {
// ...
},
});
Configuring Vitest for TypeScript is similar, but if importing from Vite defineConfig
, we need to add a reference to the Vitest type using the triple slash command at the top of the configuration file.
/// <reference types="vitest" />
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
test: {
// ...
},
});
It is worth noting that Vitest can also be configured in the project by adding the vitest.config.js
file in the root folder. If this file exists, it will take precedence over vite.config.js
to configure Vitest. Vitest also allows additional configuration, which can be found in the configuration page.
Case Demonstration: Notification
To see Vitest in action, let's create a notification component that displays three types of notifications: info
, error
和success
. Each state of this component looks like this:
info
error
success
notification.vue
The content is as follows:
<template>
<div
:class="[
'notification',
type === 'error' ? 'notification--error' : null,
type === 'success' ? 'notification--success' : null,
type === 'info' ? 'notification--info' : null,
message && message.length > 0 ? 'notification--slide' : null,
]"
>
<img
src="https://res.cloudinary.com/djalafcj9/image/upload/v1634261166/getequityV2/denied_sbmv0e.png"
v-if="type === 'error'"
/>
<img
src="https://res.cloudinary.com/djalafcj9/image/upload/v1656690265/getequityV2/Frame_irxz3e.png"
v-if="type === 'success'"
/>
<img
src="https://res.cloudinary.com/djalafcj9/image/upload/v1634261166/getequityV2/pending_ctj1ke.png"
v-if="type === 'info'"
/>
<p class="notification__text">
{{ message }}
</p>
<button
ref="closeButton"
class="notification__button"
@click="$emit('clear-notification')"
>
<img
src="https://res.cloudinary.com/djalafcj9/image/upload/v1635485821/getequityV2/close_muxdyb.png"
/>
</button>
</div>
</template>
<script>
export default {
name: "Notification",
emits: ['clear-notification'],
props: {
type: {
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;
/* margin: 0 auto; */
display: flex;
position: fixed;
/* left: 0; */
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--error {
background-color: #fdecec;
}
.notification__text {
margin: 0;
margin-left: 17px;
margin-right: auto;
}
.notification--error .notification__text {
color: #f03d3e;
}
.notification--success {
background-color: #e1f9f2;
}
.notification--success > .notification__text {
color: #146354;
}
.notification--info {
background-color: #ffb647;
}
.notification__button {
border: 0;
background-color: transparent;
}
</style>
Here, we create a component that displays a dynamic message using the message
prop. We also use the type
prop to design the background and text of this component, and use this type
prop to display the different icons we plan (error, success, info).
Finally, we have a button to dismiss the notification by emitting a custom event: clear-notification
.
What should we test?
Now that we understand the structure of the component that needs to be tested, we can think a bit more about what this component needs to do in order to function as intended.
Our test needs to check the following:
- The component renders the correct style based on the notification type.
- When
message
is empty, the notification fades away. - When the close button is clicked, the component emits an event.
To test these functions, add a notification.test.js
to the project for testing.
Install test dependencies
When writing unit tests, there may be situations where we need to replace the existing implementation of a component with a fake component that does nothing. This is called stub , and in order to use stubs in our tests, we need to access the mount
method of Vue Test Utils, which is the official testing tool library for Vue.js.
Now let's install Vue Test Utils.
Install
npm install --save-dev @vue/test-utils@next
# or
yarn add --dev @vue/test-utils@next
Now, in our test file, we can import mount
from "@vue/test-utils"
91721de228de7a2fb23cc052946e00b8---.
notification.test.js
import { mount } from "@vue/test-utils";
In testing, we also need to be able to mock the DOM. Vitest currently supports both happy-dom
and jsdom
. For this demo we will use happy-dom
and then install it:
yarn add happy-dom --dev
Once installed, we can add the following comment to the top of the test file...
notification.test.js
/**
* @vitest-environment happy-dom
*/
.or add this to the vite/vitest
config file to avoid duplication when there are multiple test files that need happy-dom
to work.
vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
test: {
environment: "happy-dom",
},
});
Since we only have one test file, we can choose the first option, so our test file content is as follows:
notification.test.js
/**
* @vitest-environment happy-dom
*/
import { mount } from "@vue/test-utils";
With these dependencies, we can now import the component we want to test.
notification.test.js
/**
* @vitest-environment happy-dom
*/
import { mount } from "@vue/test-utils";
import notification from "../components/notification.vue";
Common Vitest Methods
In order to write tests, we need to take advantage of the following common methods, which can be imported from Vitest.
-
describe
: This function accepts a name and a function to group related tests together. It comes in handy when you are writing tests for a component that has multiple test points (like logic and skins). -
test/it
: This function represents the actual code block being tested. It takes a string, usually the name or description of the test case (e.g., the correct style to render successfully) and another function, where all checks and tests take place. -
expect
: This function is used to test values or create assertions. It takes an argument x that is expected to be an actual value (string, number, object, etc.) and evaluates it using any supported method (egtoEqual(y)
, checks if x is the same as y).
So we now import these into our test file
notification.test.js
/**
* @vitest-environment happy-dom
*/
import { mount } from "@vue/test-utils";
import notification from "../components/notification.vue";
import { describe, expect, test } from "vitest";
With these functions, we start building our unit tests.
Building Vitest unit tests
First use the describe
method to group the tests.
notification.test.js
describe("notification.vue", () => {
});
Inside the describe
block, we add each actual test.
Our first use case to test is: the component renders the correct styles based on the notification type .
notification.test.js
describe("notification.vue", () => {
test("renders the correct style for error", () => {
});
});
renders the correct style for error
represents the name of the content checked by test
. It helps to provide context for what a code block inspects so it can be easily maintained and updated by people other than the original author. It also makes it easy to identify a specific failing test case.
notification.test.js
describe("notification.vue", () => {
test("renders the correct style for error", () => {
const type = "error";
});
});
In our component, a type
parameter is defined, which accepts a string that determines how things like background color, icon type, and text color are rendered on the component. type
这里,我们创建一个变量---8eef4d15aa911d51913f08b1ee812576--- ,并将我们正在处理的类型之一,error ( error
, info
, 或success
) assigned to it.
notification.test.js
describe("notification.vue", () => {
test("renders the correct style for error", () => {
const type = "error";
const wrapper = mount(notification, {
props: { type },
});
});
});
Here we use mount
to stub our component for testing purposes.
mount
accepts a component as the first argument and a list of options as the second argument. These options provide different properties, the purpose of which is to ensure that your component will work correctly in the browser.
In this list, we only need the props
attribute. We use this property because our notification.vue
component needs at least one prop to work effectively.
notification.test.js
describe("notification.vue", () => {
test("renders the correct style for error", () => {
const type = "error";
const wrapper = mount(notification, {
props: { type },
});
expect(wrapper.classes()).toEqual(
expect.arrayContaining(["notification--error"])
);
});
});
At this point, all that's left is to write an assertion, or better yet, the expected behavior of our component, which is: renders the correct style for error
.
To do this, we used the expect
method. It accepts our stub component and all the options (in our case, we named it wrapper
for easy reference).
This method can be chained to some other methods, but for this particular assertion we are rechecking that the component's class list returns an array containing this notification——error
. .
We do this using the classes
function, which returns an array containing all the classes of the component. After this, the next thing is to compare using the toEqual
function, which checks if a value X is equal to Y. In this function, we check if it returns an array containing our class: notification--error
.
Similarly, the test procedure is similar for types with type success
or info
.
import { mount } from "@vue/test-utils";
import notification from "../components/notification.vue";
import { describe, expect, test } from "vitest";
describe("notification.vue", () => {
test("renders correct style for error", () => {
const type = "error";
const wrapper = mount(notification, {
props: { type },
});
expect(wrapper.classes()).toEqual(
expect.arrayContaining(["notification--error"])
);
});
test("renders correct style for success", () => {
const type = "success";
const wrapper = mount(notification, {
props: { type },
});
expect(wrapper.classes()).toEqual(
expect.arrayContaining(["notification--success"])
);
});
test("renders correct style for info", () => {
const type = "info";
const wrapper = mount(notification, {
props: { type },
});
expect(wrapper.classes()).toEqual(
expect.arrayContaining(["notification--info"])
);
});
test("slides down when message is not empty", () => {
const message = "success";
const wrapper = mount(notification, {
props: { message },
});
expect(wrapper.classes()).toEqual(
expect.arrayContaining(["notification--slide"])
);
});
});
At this point, we've written tests to make sure our notifications are styled according to their type. When the user clicks the close button on the component, we reset the message
parameter. According to our code, we want to add or remove the notification--slide
class according to the value of this message
parameter, as follows:
notification.vue
<div
:class="[
'notification',
type === 'error' ? 'notification--error' : null,
type === 'success' ? 'notification--success' : null,
type === 'info' ? 'notification--info' : null,
message && message.length > 0 ? 'notification--slide' : null,
]"
>
//...
If we were to test this particular assertion, it would read as follows:
test("slides up when message is empty", () => {
const message = "";
const wrapper = mount(notification, {
props: { message },
});
expect(wrapper.classes("notification--slide")).toBe(false);
});
In this test code, we create a message
variable with an empty string and pass it to our component as a prop.
After that, we check our component's class array to make sure it doesn't include the notification--slide
class that is responsible for sliding our component down/out to the user's view. To do this, we use the toBe
function, which takes a value A and tries to check if it is the same as B.
We also want to test that whenever a button on the component is clicked, it emits an event:
test("emits event when close button is clicked", async() => {
const wrapper = mount(notification, {
data() {
return {
clicked: false,
};
},
});
const closeButton = wrapper.find("button");
await closeButton.trigger("click");
expect(wrapper.emitted()).toHaveProperty("clear-notification");
});
In this test block, we are using an async function because we are going to fire an event which returns a Promise and we need to wait for this Promise to resolve in order to catch the changes caused by this event. We also used the data
function and added a clicked
attribute that will be toggled when clicked.
At this point, we need to trigger the click event, we first get the button by using the find
function. This function is the same as querySelector
it takes a class, an id or an attribute and returns an element.
After the button is found, use the trigger method to fire a click event. This method accepts the name of the event to fire (click, focus, blur, keydown, etc.), executes the event and returns a promise. For this reason, we wait for this action to ensure that changes have been made to our DOM before we make assertions based on this event.
Finally, we check the list of events emitted by our component using the [emitted](https://test-utils.vuejs.org/api/#emitted)
method that returns an array. Then we check if this array contains the clear-notification
event.
Finally, we test to make sure our component renders the correct message and passes it to the message
prop.
test("renders message when message is not empty", () => {
const message = "Something happened, try again";
const wrapper = mount(notification, {
props: { message },
});
expect(wrapper.find("p").text()).toBe(message);
});
Here, we create a message
variable, assign it a random string, and pass it as a prop to our component.
Then, we search for our message text using the p
tag, since this is where the message is displayed, and check if its text is the same as message
.
We use the text
method to extract the content of this tag, which is very similar to innerText
. Finally, we use the previous function toBe
to assert that this value is the same as message
.
complete test file
After covering all of this, here is the full test file content:
notification.test.js
/**
* @vitest-environment happy-dom
*/
import { mount } from "@vue/test-utils";
import notification from "../components/notification.vue";
import { describe, expect, test } from "vitest";
describe("notification.vue", () => {
test("renders the correct style for error", () => {
const type = "error";
const wrapper = mount(notification, {
props: { type },
});
expect(wrapper.classes()).toEqual(
expect.arrayContaining(["notification--error"])
);
});
test("renders the correct style for success", () => {
const type = "success";
const wrapper = mount(notification, {
props: { type },
});
expect(wrapper.classes()).toEqual(
expect.arrayContaining(["notification--success"])
);
});
test("renders the correct style for info", () => {
const type = "info";
const wrapper = mount(notification, {
props: { type },
});
expect(wrapper.classes()).toEqual(
expect.arrayContaining(["notification--info"])
);
});
test("slides down when message is not empty", () => {
const message = "success";
const wrapper = mount(notification, {
props: { message },
});
expect(wrapper.classes()).toEqual(
expect.arrayContaining(["notification--slide"])
);
});
test("slides up when message is empty", () => {
const message = "";
const wrapper = mount(notification, {
props: { message },
});
expect(wrapper.classes("notification--slide")).toBe(false);
});
test("emits event when close button is clicked", async() => {
const wrapper = mount(notification, {
data() {
return {
clicked: false,
};
},
});
const closeButton = wrapper.find("button");
await closeButton.trigger("click");
expect(wrapper.emitted()).toHaveProperty("clear-notificatioon");
});
test("renders message when message is not empty", () => {
const message = "Something happened, try again";
const wrapper = mount(notification, {
props: { message },
});
expect(wrapper.find("p").text()).toBe(message);
});
});
A few things to note:
- We utilize
mount
to stub the component we want to test, which is provided by Vue Test Utils. (yarn add --dev @vue/test-utils@next
)
run the test
Now that the tests have been written, they need to be run. To achieve this, we go to the package.json
, 在我们的scripts
section and add the following lines.
"scripts": {
"test": "vitest",
"coverage": "vitest run --coverage"
},
If we run yarn vitest
or yarn test
in the terminal, our test file will be run and we should see test results and failures.
At this point, we have successfully run our first test with Vitest. One thing to note from the results is that due to Vitest's intelligence and instant watch mode, this command only needs to be run once and is re-run whenever we make updates and modifications to the test files.
Summarize
Unit testing our application with Vitest is seamless and requires fewer steps to get up and running than alternatives like Jest. Vitest also makes it easy to migrate existing tests from Jest to Vitest without additional configuration.
Author: Timi Omoyeni Translator: Front-end Xiaozhi Source: vuemastery
Original: https://www.vuemastery.com/blog/getting-started-with-vitest
The bugs that may exist after the code is deployed cannot be known in real time. In order to solve these bugs afterwards, a lot of time is spent on log debugging. By the way, I recommend a useful bug monitoring tool , Fundebug .
comminicate
If you have dreams and dry goods, you can search for [Great Move to the World] on WeChat and pay attention to this Shawanzhi who is still washing dishes in the early hours of the morning.
This article GitHub https://github.com/qq449245884/xiaozhi has been included, there are complete test sites, materials and my series of articles for interviews with first-line manufacturers.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。