3

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

image.png

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 (eg toEqual(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.

image.png

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.

image.png

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.


王大冶
68.1k 声望105k 粉丝