乐趣区

关于前端:试试使用-Vitest-进行组件测试确实很香

新出了一个系列:Vue2 与 Vue3 技巧小册

微信搜寻【大迁世界】, 我会第一工夫和你分享前端行业趋势,学习路径等等。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

什么是 Vitest?

自从 尤大 的构建工具 Vite 取得了微小的人气,当初有了一个由它驱动的极快的单元测试框架。Vitest

Vitest 与 Jest 兼容,具备开箱即用的 ESM、Typescript 和 JSX 反对,并且由 esbuild 提供反对。它在测试过程中应用 Vite 开发服务器来转换你的文件,并监听你的应用程序的雷同配置(通过vite.config.js),从而打消了应用 Jest 等测试替代品所波及的反复工作。

为什么抉择 Vitest?

Vite 是一个构建工具,旨在为古代 web 我的项目提供更快、更精简的开发体验,它开箱即用,反对常见的 web 模式、glob导入和 SSR 等性能。它的许多插件和集成正在促成一个充满活力的生态系统。

但这导致了一个新问题:如何在 Vite 上编写单元测试。

将 Jest 等框架与 Vite 一起应用,导致 Vite 和 Jest 之间有很多反复的配置,而 Vitest 解决了这一问题,它打消了为咱们的应用程序编写单元测试所需的额定配置。Vitest 应用与 Vite 雷同的配置,并在开发、构建和测试时共享一个独特的转换管道。它还能够应用与 Vite 雷同的插件 API 进行扩大,并与 Jest 的 API 兼容,以不便从 Jest 迁徙,而不须要做很多重构工作。

因而,Vitest 的速度也十分快。

如何应用 Vitest 来测试组件

装置 Vitest

在我的项目中应用 Vitest 须要 Vite >=v2.7.10Node >=v14 能力工作。

能够应用 npmyarnpnpm 来装置 Vitest,依据本人的爱好,在终端运行以下命令:

NPM

npm install -D vitest

YARN

yarn add -D vitest

PNPM

pnpm add -D vitest

Vitest 配置

装置完 Vitest 后,须要将其增加到 vite.config.js 文件中:

vite.config.js

import {defineConfig} from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({plugins: [vue()],
    //add test to vite config
    test: {// ...},
});

为 TypeScript 配置 Vitest 是相似的,但如果从 Vite 导入 defineConfig,咱们须要在配置文件的顶部应用三斜线命令增加对 Vitest 类型的援用。

/// <reference types="vitest" />
import {defineConfig} from "vite";
import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/

export default defineConfig({plugins: [vue()],
    test: {// ...},
});

值得注意的是,Vitest 也能够在我的项目中通过在根文件夹中增加 vitest.config.js 文件来配置。如果这个文件存在,它将优先于 vite.config.js 来配置 Vitest。Vitest 也容许额定的配置,能够在配置页面中找到。

事例演示:Notification

为了看看 Vitest 的运作状况,咱们创立一个显示三种类型告诉的告诉组件:infoerror 和 success。这个组件的每个状态如下所示:

info

error

success

notification.vue 内容如下:

<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>

在这里,咱们应用 message prop 创立了一个显示动静音讯的组件。咱们还利用 type prop 来设计这个组件的背景和文本,并利用这个 type prop 显示咱们打算的不同图标(error, success, info)。

最初,咱们有一个按钮,用来通过收回一个自定义事件:clear-notification来解除告诉。

咱们应该测试什么?

当初咱们对须要测试的组件的构造有了理解,咱们能够再思考一下,这个组件须要做什么,以达到预期的性能。

咱们的测试须要查看以下内容:

  • 该组件依据告诉类型渲染出正确的款式。
  • message 为空时,告诉就会逐步隐没。
  • 当敞开按钮被点击时,该组件会收回一个事件。

为了测试这些性能,在我的项目中增加一个 notification.test.js 用于测试。

装置测试依赖项

在编写单元测试时,可能会有这样的状况:咱们须要用一个什么都不做的假组件来替换组件的现有实现。这被称为 stub(存根),为了在测试中应用存根,咱们须要拜访 Vue Test Utils 的 mount 办法,这是 Vue.js 的官网测试工具库。

当初咱们来装置 Vue Test Utils。

装置

npm install --save-dev @vue/test-utils@next

# or

yarn add --dev @vue/test-utils@next

当初,在咱们的测试文件中,咱们能够从 "@vue/test-utils" 导入 mount

notification.test.js

import {mount} from "@vue/test-utils";

在测试中,咱们还须要可能模仿 DOM。Vitest 目前同时反对 happy-domjsdom。对于这个演示,咱们将应用happy-dom,而后装置它:

yarn add happy-dom --dev

装置后,咱们能够在测试文件的顶部增加以下正文 …

notification.test.js

/**
 * @vitest-environment happy-dom
 */

. 或者将此增加到 vite/vitest 配置文件中,以防止在有多个须要 happy-dom 工作的测试文件时呈现反复状况。

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",},
});

因为咱们只有一个测试文件,所以咱们能够抉择第一个选项,所以咱们测试文件内容如下:

notification.test.js

/**
 * @vitest-environment happy-dom
 */

import {mount} from "@vue/test-utils";

有了这些依赖关系,咱们当初能够导入咱们要测试的组件。

notification.test.js

/**
 * @vitest-environment happy-dom
 */

import {mount} from "@vue/test-utils";
import notification from "../components/notification.vue";

常见的 Vitest 办法

为了编写测试,咱们须要利用以下常见的办法,这些办法能够从 Vitest 导入。

  • describe:这个函数承受一个名字和一个函数,用于将相干的测试组合在一起。当你为一个有多个测试点(如逻辑和外观)的组件编写测试时,它就会很不便。
  • test/it:这个函数代表被测试的理论代码块。它承受一个字符串,通常是测试案例的名称或形容(例如,渲染胜利的正确款式)和另一个函数,所有的检查和测试在这里进行。
  • expect:这个函数用于测试值或创立断言。它承受一个预期为理论值(字符串、数字、对象等)的参数x,并应用任何反对的办法对其进行评估(例如toEqual(y),查看 x 是否与 y 雷同)。

因而,咱们当初将这些导入咱们的测试文件中

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";

有了这些函数,咱们开始构建咱们的单元测试。

建设 Vitest 单元测试

首先应用 describe 办法将测试分组。

notification.test.js

describe("notification.vue", () => {});

describe 块内,咱们增加每个理论的测试。

咱们第一个要测试的用例是:组件依据告诉类型渲染出正确的款式

notification.test.js

describe("notification.vue", () => {test("renders the correct style for error", () => {});
});

renders the correct style for error 示意 test 所查看的内容的 name。它有助于为代码块查看的内容提供上下文,这样就能够由原作者以外的人轻松保护和更新。它也使人们容易辨认一个特定的失败的测试案例。

notification.test.js

describe("notification.vue", () => {test("renders the correct style for error", () => {const type = "error";});
});

在咱们组件中,定义了一个 type 参数,它承受一个字符串,用来决定诸如背景色彩、图标类型和文本色彩在组件上的渲染。在这里,咱们创立一个变量 type,并将咱们正在解决的类型之一,error(error, info, 或 success)调配给它。

notification.test.js

describe("notification.vue", () => {test("renders the correct style for error", () => {
        const type = "error";
        const wrapper = mount(notification, {props: { type},
        });
    });
});

在这里,咱们应用 mount 来存根咱们的组件,以便进行测试。

mount 承受组件作为第一个参数,承受一个选项列表作为第二个参数。这些选项提供了不同的属性,目标是确保你的组件能在浏览器中失常工作。

在这个列表中,咱们只须要 props 属性。咱们应用这个属性是因为咱们的 notification.vue组件至多须要一个 prop 能力无效工作。

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"])
        );
    });
});

在这一点上,剩下的就是写一个断言,或者更好的是,写出咱们组件的预期行为,即:renders the correct style for error

为了做到这一点,咱们应用了 expect 办法。它承受咱们的存根组件和所有的选项(在咱们的例子中,咱们把它命名为 wrapper 以不便参考)。

这个办法能够被链接到其余一些办法上,然而对于这个特定的断言,咱们要从新查看组件的类列表是否返回一个蕴含这个 notification——error 的数组。。

咱们应用 classes 函数来实现这一点,该函数返回蕴含该组件所有类的数组。在这之后,下一件事就是应用 toEqual 函数进行比拟,它查看一个值 X 是否等于 Y。在这个函数中,咱们查看它是否返回一个蕴含咱们的类的数组: notification--error

同样,对于 type 为 successinfo 类型,测试过程也差不多。

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"])
        );
    });
});

到这,咱们曾经写好了测试,以确保咱们的告诉是依据其类型来进行款式设计的。当用户点击组件上的敞开按钮时,咱们会重置 message 参数。依据咱们的代码,咱们要依据这个 message 参数的值来增加或删除 notification--slide 类,如下所示:

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,
    ]"
  >
//...

如果咱们要测试这个特定的断言,它的内容如下:

test("slides up when message is empty", () => {
        const message = "";
        const wrapper = mount(notification, {props: { message},
        });
        expect(wrapper.classes("notification--slide")).toBe(false);
    });

在这段测试代码中,咱们用一个空字符串创立一个 message 变量,并把它作为一个 prop 传递给咱们的组件。

之后,咱们查看咱们组件的类数组,确保它不包含 notification--slide 类,该类负责使咱们的组件向下 / 向外滑动到用户的视图。为了做到这一点,咱们应用 toBe 函数,它接管一个值A,并试图查看它是否与 B 雷同。

咱们还想测试一下,每当组件上的按钮被点击,它就会收回一个事件:

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");
    });

在这个测试块中,咱们应用了一个 async 函数,因为咱们将触发一个事件,它返回一个 Promise,咱们须要期待这个 Promise 的解决,以便捕获这个事件所引起的变动。咱们还应用了 data 函数,并增加了一个 clicked 属性,当点击时将被切换。

到这,咱们须要触发这个点击事件,咱们首先通过应用 find 函数来取得按钮。这个函数与 querySelector 雷同,它承受一个类、一个 id 或一个属性,并返回一个元素。

在找到按钮后,应用 trigger 办法来触发一个点击事件。这个办法承受要触发的事件名称(click, focus, blur, keydown 等),执行这个事件并返回一个 promise。出于这个起因,咱们期待这个动作,以确保在咱们依据这个事件做出断言之前,曾经对咱们的 DOM 进行了扭转。

最初,咱们应用返回一个数组的 [emitted](https://test-utils.vuejs.org/api/#emitted) 办法查看咱们的组件所收回的事件列表。而后咱们查看这个数组是否包含 clear-notification 事件。

最初,咱们测试以确保咱们的组件渲染出正确的音讯,并传递给 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);
    });

这里,咱们创立了一个 message 变量,给它调配了一个随机字符串,并把它作为一个 prop 传递给咱们的组件。

而后,咱们应用 p 标签搜寻咱们的音讯文本,因为这里是显示音讯的中央,并查看其文本是否与 message 雷同。

咱们应用 text 办法提取这个标签的内容,这和 innerText很类似。最初,咱们应用后面的函数 toBe 来断言这个值与 message 雷同。

残缺的测试文件

在涵盖所有这些之后,上面是残缺的测试文件内容:

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);
    });
});

有几件事须要留神:

  • 咱们利用 mount 来存根咱们要测试的组件,它是由 Vue Test Utils 提供的。(yarn add --dev @vue/test-utils@next)

运行测试

当初曾经实现了测试的编写,须要运行它们。要实现这一点,咱们去 package.json在咱们的 scripts 局部增加以下几行。

"scripts": {
    "test": "vitest",
    "coverage": "vitest run --coverage"
},

如果在终端运行 yarn vitestyarn test,咱们的测试文件就会被运行,咱们应该看到测试后果和故障。

到这,咱们曾经胜利地应用 Vitest 运行了咱们的第一次测试。从后果中须要留神的一点是,因为 Vitest 的智能和即时察看模式,这个命令只须要运行一次,并在咱们对测试文件进行更新和批改时被从新运行。

总结

应用 Vitest 对咱们的应用程序进行单元测试是无缝的,与 Jest 等替代品相比,须要更少的步骤来启动和运行。Vitest 还能够很容易地将现有的测试从 Jest 迁徙到 Vitest,而不须要进行额定的配置。

作者:Timi Omoyeni 译者:前端小智 起源:vuemastery

原文:https://www.vuemastery.com/bl…

代码部署后可能存在的 BUG 没法实时晓得,预先为了解决这些 BUG,花了大量的工夫进行 log 调试,这边顺便给大家举荐一个好用的 BUG 监控工具 Fundebug。

交换

有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq44924588… 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

退出移动版