用Vitest测试一个Svelte应用程序

772 阅读17分钟

Vite已经成为前端工具链的关键。这个构建工具也被SvelteKit采用,它是Svelte的官方应用框架。因此,我认为可以这样说,Vite已经成为Svelte的首选开发工具。

Vitest是一个相对较新的、以Vite为基础的单元测试框架。它承诺成为Vite的理想测试伙伴,但它能实现吗?

在这篇文章中,我将探讨以下问题。

Svelte和Vite的测试框架

目前,Svelte并不推荐特定的单元测试框架,也不主张采用特定的测试策略。官方网站提供了一些基本建议。理论上,你可以使用任何你喜欢的JavaScript单元测试库进行测试,而你的前端工具链最终会吐出一个虚无的JavaScript捆绑包,对吗?

好吧,这就是问题的关键所在;你正在处理与JavaScript相关的不同的语法和格式的排列组合。这些格式会被你的工具链转化为另一种格式。当工具链中的工具不使用相同的语法,并且合作不力时,将它们集成到一个工具链中是很困难的。

Vite通过使用本地ECMAScript模块(ESM)提供按需的文件服务,提供了一个快速的开发环境。它可以对Svelte进行即时的原子式翻译,并将其翻译成JavaScript。许多单元测试框架都是使用CommonJS模块构建的这是一个备用的模块标准。使用Vite来管理文件的翻译,然后将它们传递给用不同标准构建的测试框架,会产生摩擦。

引用Vitest团队的说法

Vite的单元测试故事还不清楚。现有的选项,如Jest,是在不同的环境下创建的。Jest和Vite之间有很多重复的地方,迫使用户配置两个不同的管道。

Jest可能是最受欢迎的单元测试框架;它在2021年的JS状态调查中名列前茅Jest与Vite的集成是不完善的。有一个名为Vite-jest的库,旨在为Jest提供一流的Vite集成;然而,它目前是一项正在进行的工作。

Vite-jest没有提到Svelte,也不可能与Svelte一起工作。对于SvelteKit,有一个实验性的库叫svelte-add-jest。底线是,目前还没有一个明确、可靠的方法将Jest与Vite和Svelte一起使用。

在任何情况下,正如Vitest团队所提到的,Jest和Vite之间的工作是重复的。你最终会有一个用于开发的管道和一个用于测试的管道。对于Jest来说,你可能会使用Babel和一个插件来把Svelte翻译成JavaScript。然后,你需要把一些配套的部分拼接起来,使其发挥作用。这就有点像Rube Goldberg机器

我在另一篇文章《用Jest测试Svelte应用程序》中提到了这一点。我创建了一个启动模板,将Vite、Svelte、Jest和Svelte测试库放在一起使用。它需要大约10个依赖项,以及两个额外的配置文件(.babelrcjest.config.json )来创建测试管道。

拥有这样的设置是很脆弱的,特别是当你不完全了解这些工具的内部工作原理时。我会祈祷这些依赖关系中的一个变化不会破坏这个链条!更多的环节,更多的失败的可能性如果你成为一个项目的维护者,这种感觉可不好。

Modern Digital Infrastructure Comic

图片来源:XKCD

总而言之,一个更加集成的解决方案是最好的。一个Vite-native的解决方案甚至更好。如果有一个与Jest兼容的API的Vite-native解决方案,那就更好了。在一个单一的配置文件中完成这一切将是涅槃。这有可能是Vitest所能提供的。

首先,让我们浏览一下API,看看我们的测试会是什么样子。

用Vitest编写测试

你可以阅读Vitest的API文档以获得更详细的信息,但为了回顾关键点,我将提供一个快速的API概述。

默认情况下,Vitest寻找以.spec.js.test.js 结尾的文件名。如果你愿意,你可以对它进行不同的配置。我把我的测试文件命名为<component-name>.spec.js ,并把它们与我的组件文件放在一起。

该API与Chaiassertions和Jest expect兼容。如果你以前使用过这些,这将是很熟悉的。关键的部分是。

  • describe 块:用于将相关的测试组合成一个测试套件;一个套件可以让你组织你的测试,所以报告是清晰的;如果你想做进一步的聚合,你也可以嵌套它们
  • test 或 块:用于创建一个单独的测试it
  • expect 语句:当你写测试时,你需要检查值是否符合某些条件--这些被称为断言; 函数提供了对几个expect "匹配器 "函数的访问,让你验证不同类型的东西(例如, , )。toBeNull toBeTruthy

下面是一个基本的骨架,即对我们的Todo 组件进行测试的测试套件的样子。

import {describe, expect, it} from 'vitest';
import Todo from "./Todo.svelte";

describe("Todo", () => {
    let instance = null;

    beforeEach(() => {
        //create instance of the component and mount it
    })

    afterEach(() => {
        //destory/unmount instance
    })

    test("that the Todo is rendered", () => {
        expect(instance).toBeDefined();
    })
})

虽然你可以单独使用Vitest来运行测试,但你必须创建一个组件的实例,然后将其安装到文档中。我们可以在beforeEach 函数中这样做。你可能还需要在afterEach 中销毁或卸载这个实例。这很麻烦;这是最好避免的那种繁文缛节。

更常见的是使用Svelte测试库,它有助于推动你实现良好的测试实践。它有一个render 函数,为我们处理组件的渲染,并使用jsdom在内存中完成。你还可以通过jest-dom库获得更方便的匹配器函数,如toBeInTheDocument()

首先,你要用这个命令安装这些库。

npm i -D @testing-library/svelte jest-dom jsdom

我并不完全确定你是否需要自己安装jsdom。我可能被提示要安装它;我不记得确切的情况了!"。

现在,我们可以专注于编写测试。你可以看到,我们把我们的组件名称和道具传递给render 函数来渲染我们的组件。然后,我们可以通过一个隐含的screen 变量来访问HTML输出。我们可以运行查询方法来找到我们想测试的页面元素。

import { render, screen } from "@testing-library/svelte";
import Todo from "./Todo.svelte";

describe("Todo", () => {
  const todoDone = { id: 1, text: "buy milk", done: true };
  const todoNotDone = { id: 2, text: "do laundry", done: false };

  test("shows the todo text when rendered", () => {
    render(Todo, { props: { todo: todoDone } });

    expect(screen.getByLabelText("Done")).toBeInTheDocument(); // checkbox
    expect(screen.getByText(todoDone.text)).toBeInTheDocument();
  });
});

将一个项目从Jest迁移到Vitest

在Vitest网站上有一个简短的迁移指南

(深呼吸)

我们走吧!

我将分叉一个我之前制作的Todo应用,然后用Jest和Svelte测试库进行测试。我在测试中使用了<component_name>.spec.js 命名规则,与配套的组件一起。它的覆盖率为98.07%。

Todo应用程序有以下功能。

  1. 列出todos。当列表上没有项目时,应用程序会显示 "恭喜你,全部完成!"的信息。
  2. 记录进度。用户可以标记已完成的todos,并取消标记仍需关注的todos;已完成的todos的风格不同,有灰色文本和删除线装饰。
  3. 添加新的todos。用户可以添加一个新的待办事项,但该应用程序禁止添加一个空的待办事项。

下面是组件的概述。

Todos List

安装

首先,我们安装Vitest。我是用npm安装的,但你也可以用你选择的软件包管理器。

# with npm
npm i -D vitest

# or with yarn
yarn add -D vitest

# or with pnpm
pnpm add -D vitest

接下来,让我们检查一下我们是否有所有东西的最新版本。Vitest需要Vite v2.7.10+和Node v14+。

对于Vite,我使用的是v2.6.4,所以我需要更新它。最快的方法是运行:npm i -D vite

我使用的Node是v14.18.1,所以那里不需要更新。如果你需要更新Node,你可以遵循这个指南

配置

根据Vitest的迁移指南

Jest默认启用了他们的globals API。Vitest则没有。你可以通过配置设置启用globals [globals](https://vitest.dev/config/#globals)配置设置启用globals,或者更新你的代码,使用从vitest 模块导入的方式。

让我们启用 [globals](https://vitest.dev/config/#globals)选项。下面是我们的vite.config.js 文件在启用了globals选项之后的样子。

import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [svelte()],
  test: {
    globals: true,
  },
});

现在,让我们试着运行Vitest;命令:npx vitest run 将运行一次测试。

运行这个命令后,所有的16个测试都失败了!

All Tests Fail

有两种类型的错误。

  1. 无法找到@testing-library/svelte Node模块。这是我们正在使用的Svelte测试库;我们可以再来看看这个
  2. ReferenceError: 没有定义文件。document 对象是在jsdom中定义的;这个是用来模拟DOM API的

我猜想,环境默认设置为Node,这与Jest最近的举动相同。让我们把环境变量改为jsdom

export default defineConfig({
  plugins: [svelte()],
  test: {
    globals: true,
    environment: "jsdom",
  },
});

这样就解决了文档错误的问题。现在,有四个测试通过了。但是,我们仍然有七个失败。

其余的错误是不同的;它们被归类为无效的ChaipropertyError 。例如,无效的Chai属性:toBeInTheDocument

toBeInTheDocumenttoBeEnabled 函数来自jest-dom库,该库是 Svelte 测试库的一个配套。我们之前在测试文件中不需要导入语句,因为我们将Jest配置为包括jest-dom。下面是我们的Jest配置文件中的选项(jest.config.json)。

"setupFilesAfterEnv": ["@testing-library/jest-dom/extend-expect"]

为了在Vitest中做同样的事情,我们可以配置一个设置文件,在每个测试文件之前运行。我们可以通过 [setupFiles](https://vitest.dev/config/#setupfiles)选项来设置。

我们将创建一个src/setuptest.js 文件,其中包括以下import 语句。

import "@testing-library/jest-dom";

我们可以在vite.config.js 文件中更新我们的test 对象,使它看起来像这样。

export default defineConfig({
  plugins: [svelte()],
  test: {
    globals: true,
    environment: "jsdom",
    setupFiles: ["src/setupTest.js"],
  },
});

现在,有11个测试通过了,只有一个没有通过!我们几乎成功了!

FAIL  src/components/Todo.spec.js [ src/components/Todo.spec.js ]
Error: Error: Cannot find module @testing-library/svelte/node_modules/@testing-library/dom imported from file:///home/rob/programming/workspace/js/svelte/svelte-todo-with-tests-(vitest), file:///home/rob/programming/workspace/js/svelte/svelte-todo-with-tests-(vitest)/node_modules
 ❯ MessagePort.[nodejs.internal.kHybridDispatch] internal/event_target.js:399:24

最后一个错误与Todo.spec.js 文件中的第二个import 语句有关。

import { render, screen } from "@testing-library/svelte";
import { fireEvent } from "@testing-library/svelte/node_modules/@testing-library/dom";

我不知道为什么我有第二个这样的import 语句!fireEvent 与第一条语句属于同一个库,所以我们应该可以把两条语句都浓缩到这样的一个import

import { render, screen, fireEvent } from "@testing-library/svelte";

我们好了吗?

All Tests Pass

是的!所有16个测试都通过了!🙌

现在,让我们整理一下我们在package.json 文件中的scripts

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

覆盖率报告需要一个额外的包,c8。让我们安装该软件包并运行覆盖率。

npm i -D c8
npm run coverage

下面是输出结果。

Output

我们现在已经升级到了100%的覆盖率(在Jest中是98.07%)。奖励积分!🎁

最后,我们可以删除Jest相关的依赖和配置。下面是我用来清理的命令。

rm jest.config.json .babelrc
npm uninstall -D @babel/preset-env babel-jest jest jest-transform-stub svelte-jester

我们删除了两个配置文件和五个依赖项。我感觉我在漂浮!🎈😄

这里是这个项目的GitHub repo

对Vitest迁移经验的看法

我给Vitest的迁移体验打7分(满分10分)。如果你使用的是jsdom、Svelte测试库或jest-dom,那么迁移指南就会让你感到不足。这是几个额外的、简短的步骤,可能会让你抓耳挠腮。如果该文件得到改进,我会给它打九分。能够在不编辑测试文件的情况下让一切都正常工作,这是一个令人惊喜的事情。

Vitest的功能表现

到目前为止,我们已经证实了Vitest的几个功能是可以工作的。

  1. Svelte的组件测试
  2. 在Vitest中使用Svelte测试库
  3. 使用jsdom对内存中的文件进行测试
  4. Chai内置断言和Jest期望兼容的API
  5. 通过c8进行本地代码覆盖

现在,让我们用我们的Todo项目来看看Vitest的其他一些重要功能。我正在尝试衡量Vitest是否可以投入生产。目前,Vitest的版本是0.14.1,所以我猜它仍被认为是测试版。

智能和即时观察模式

就像Vite在浏览器中的工作方式一样,Vitest也知道你的模块的图形。这使得Vitest能够进行智能检测,只重新运行与变化有关的测试。根据Vitest团队的说法,它"......感觉几乎就像HMR,不过是为了测试"。

让我们在观察模式下运行Vitest与npm run test ,并改变其中一个组件文件。

让我们打开AddTodo.svelte 文件,做一个突破性的改变。我将从button 中删除disabled 的属性,这应该会触发一个失败的测试。

Failing Test

Vitest只重新运行相关的测试套件(AddTodoApp ),我们得到了一个失败的测试案例,正如预期的那样运行测试套件需要446ms,而运行所有的测试套件至少需要几秒钟!这个改进对生产力来说是非常好的。这一改进对于生产力来说是非常好的。

并发测试

我们可以将.concurrent 添加到一个套件或单个测试中,以并行运行它们。

import { render, screen, fireEvent } from "@testing-library/svelte";
import App from "./App.svelte";

describe.concurrent("App", () => {
  /* all tests run in parallel */
})

让我们看看这是否加快了测试的速度!理论上,我的应用程序应该能够并发地运行所有的测试。

作为一个基准,从冷启动开始,运行我的四个测试套件需要5.11秒。将测试套件改为并发运行后,运行时间为3.97秒。它缩短了一秒多的时间🎉

内建的TypeScript支持

测试一个TypeScript-Svelte应用程序似乎工作得很好。Johnny Magrippis有一个关于这个主题的详尽视频教程。他用SvelteKit制作了一个小型的货币仪表盘,并用Vitest进行测试。这些代码可以在这个GitHub repo中找到。

测试过滤。在命令行上定位测试

在命令行上锁定的测试文件可以通过传递一个名称/模式作为参数进行过滤。例如,下面的命令将只运行包含List 的文件。

npx vitest List

在我们的例子中,只有TodoList.spec 文件被运行。

跳过套件和测试

你也可以在describetest 函数中添加.skip 来避免运行某些套件或测试。

Skip Function

在这里,我通过在describe 函数中添加.skip 来跳过Todo 测试套件。正如你在上面看到的,输出告诉你哪些测试套件和测试被跳过。而且,颜色编码让你很容易发现它们。

在SvelteKit中使用Vitest

要在SvelteKit中使用Vitest,你需要添加测试依赖项。

npm i -D vitest @testing-library/svelte jest-dom jsdom

接下来,我们需要添加我之前在Jest到Vitest迁移示例中分享的相同配置。但是,我们到底该把配置放在哪里呢?

快速和肮脏的方法

我采取的方法是把我的配置放在项目根部的一个叫做vitest.config.js 的文件中。这个文件包含以下代码。

import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [svelte({ hot: !process.env.VITEST })],
  test: {
    globals: true,
    environment: "jsdom",
    setupFiles: ["src/setupTest.js"],
  },
});

我还添加了导入jest-domsrc/setupTest.js 文件。这使我们不必在每个文件中添加import 语句。src/setupTest.js 文件有以下内容。

import "@testing-library/jest-dom";

这很有效,但我是不是做了什么可疑的事情?

我不确定。但是,我看到有人提到另一种方法。

正确的方法是什么?

SvelteKit的配置存在于 [svelte.config.js](https://kit.svelte.dev/docs/configuration)文件中,该文件位于项目根部。有一个Vite选项,以Vite配置对象为其值。如果能在这里添加与Vite相关的选项就更好了,这样我们就能在一个单一的配置中获得所有的东西。我试过了,但没有成功。

我注意到Johnny Magrippis添加了一个名为vitest-svelte-kit的库,以便将Vitest相关的选项直接添加到 [svelte.config.js](https://kit.svelte.dev/docs/configuration)文件中。然而,这并不能自动工作。要使用这种技术,你需要做以下工作。

首先,安装vitest-svelte-kit。

npm i -D vitest-svelte-kit

接下来,将测试相关的东西添加到你的svelte.config.js 文件中的vite 对象中。

import adapter from "@sveltejs/adapter-auto"
import preprocess from "svelte-preprocess"

/** @type {import('@sveltejs/kit').Config} */
const config = {
    // Consult https://github.com/sveltejs/svelte-preprocess
    // for more information about preprocessors
    preprocess: preprocess(),

    kit: {
        adapter: adapter(),
        vite: {
            test: {
                environment: "jsdom",
                globals: true,
                setupFiles: 'src/setupTests.ts',
            },
        },
    },
}

export default config

然后,创建一个vitest.config.js 文件,从库中暴露一个函数。

import { extractFromSvelteConfig } from "vitest-svelte-kit"

export default extractFromSvelteConfig()

我想vitest-svelte kit还没有完全解决所有的问题,但就我使用它的情况来看,它对我来说是很好的。

以后,我希望能有一个加法器。添加器是向SvelteKit项目添加集成的一种简单方式。一个添加器将使你在命令行上创建新的应用程序时包含Vitest成为可能。这将提供一个成熟的路径。所以,我们还没有完全达到使用单一配置文件的目的。

使用Vitest与相关的集成

我很惊讶地看到Vitest在浏览器和你的IDE中的测试已经得到了很好的支持。现在,让我们来看看Vitest是如何与Web UI和IDE整合的。

网页界面的集成

你可以在Web UI中使用Vitest。它需要一个额外的软件包,而且你应该用--ui 标志来运行它。

npm i -D @vitest/ui

接下来,你可以通过传递--ui 标志来启动Vitest。

npx vitest --ui

然后,你可以访问Vitest的用户界面,网址是 [http://localhost:51204/__vitest__/](http://localhost:51204/__vitest__/).

然而,我在Ubuntu的任何浏览器中都没有看到结果!我只看到一条细细的绿线。 🙈 我只看到一条细细的绿线!

集成开发环境整合

你也可以在IDE中使用Vitest。有一个VS Code的扩展和一个JetBrains产品的插件。

我把VS Code的扩展拿出来转了转,效果不错。它提供了一个侧边栏视图,你可以在那里运行测试。它可以启动一个调试会话,把你带到失败的测试的代码中。

Debugging Session

总结

我对Vitest印象深刻。它的速度很快,智能手表模式很好,比竞争对手更容易配置,而且它融合了其他框架的最佳实践,提供了一种熟悉的测试体验。

能够将一个现有的项目从Jest迁移到Vitest,而不必改变测试文件,这是一个巨大的胜利。我认为将Vitest与Svelte和SvelteKit一起使用几乎是没有问题的。

虽然我把Vitest用于一个小的应用程序,但我不能说在一个更大的项目上工作时是否有任何问题,也不能说它如何管理更复杂的测试案例。我想,如果你把它用在一个生产应用上,你就会处于一个开拓性的空间;所以这里面有一些风险因素。

如果你已经在你的项目中使用了Jest,你可以在Jest旁边试用Vitest,而不需要去管你的测试文件。如果你在这个阵营中,这个策略将使你能够减轻风险。

总的来说,我建议将Vitest与Svelte一起使用。它有资金支持、全职团队成员和一个强大的社区,我期待Vitest有一个光明的未来!

The postTesting a Svelte app with Vitestappeared first onLogRocket Blog.