使用Vitest,PostgreSQL和Prisma创建一个完整的Nodejs测试环境
#node #postgres #测试 #prisma

介绍

在软件开发领域,测试是确保产品质量和可靠性的关键方面。单位测试和端到端测试是开发人员使用的两种重要类型的测试类型,然后将其代码释放到生产中之前。单元测试专注于隔离测试单个功能和模块,而端到端测试模拟用户行为并测试系统的整体工作方式,包括其与外部系统和依赖关系的相互作用。在本文中,我们将探讨如何使用Postgres和Prisma创建完整的Node.js测试环境。通过建立强大的测试基础架构,我们可以尽早捕获错误,并确保我们的代码在生产中具有性能和可靠性。我们将浏览设置测试环境所需的步骤。借助正确的工具和思维方式,测试可以成为开发过程中不可或缺的一部分,帮助您创建满足用户需求的高质量软件。

铺平道路

让我们配置必要的工具。

Prisma

因此,我们要做的第一件事就是安装所需的依赖项。我将在此处使用NPM作为软件包管理器,但请随时使用您的首选工具。

要开始,我们需要通过运行:
来安装项目中的Prisma CLI

npm i prisma -D

这样,我们可以在项目中执行像npx prisma migrate dev和其他类似的命令。由于我们仅在开发环境中使用Prisma CLI,因此我们可以将其作为开发依赖性作为recommended by the Prisma documentation

现在我们可以使用:
启动Prisma

npx prisma init

这将创建一个新的prisma/schema.prisma文件。这是一个声明的配置文件,我们可以在其中使用Prisma Schema语言来定义数据库架构。

ðâ€strong> vscode用户的提示:作为额外的步骤,您可以安装Prisma extension,也可以将其添加到VSCODE配置中:

"[prisma]": {
  "editor.formatOnSave": true
},

这将确保每次保存时始终为您的schema.prisma文件进行自动格式。

默认情况下,Prisma已经将数据库提供商设置为postgresql,因此您的schema.prisma文件应该看起来像这样:

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

随意到那里的define your models

接下来,我们可以执行Prisma生成命令:

npx prisma generate

这将读取我们的schema.prisma文件并根据其定义的数据模型生成Prisma客户端。

我们需要做的下一个事情当然是配置我们的数据库。

默认情况下,Prisma已经生成了一个带有示例PostgreSQL数据库URL的.env文件。它应该看起来像这样:

# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"

Postgresql

要设置我们的数据库,我将在此处使用Docker。如果您已经在计算机中安装了PostgreSQL,请随时使用它。如果您想像我一样使用Docker,可以关注安装教程here

安装了Docker,我们可以通过运行:
来拉出PostgreSQL图像

docker pull bitnami/postgresql

然后通过运行
创建我们的Docker容器

docker run --name node-test-env-pg -e POSTGRESQL_USERNAME=docker -e POSTGRESQL_PASSWORD=docker -e POSTGRESQL_DATABASE=node-test-env -p 5432:5432 -d bitnami/postgresql

这将创建一个具有名称为“ node-test-env-pg”,用户名“ docker”,“ docker”,“ docker”和数据库的容器我们刚刚拉出的Bitnami/postgresql图像的使用。

现在,我们可以更改Prisma创建的.env文件中的DATABASE_URL,并使用新连接URL:

DATABASE_URL="postgresql://docker:docker@localhost:5432/node-test-env?schema=public"

用户名从“ johndoe”更改为“ docker”,密码也从“ randompassword”更改为“ docker”,数据库名称从“ mydb”更改为“ node-test-env”,这是我们在创建Docker容器时指定的参数。

要测试一切是否正常工作,我们现在可以执行项目内的Prisma迁移命令:

npx prisma migrate dev

为您的第一个迁移选择名称并确认。这将为您创建并运行数据库迁移。如果您尚不熟悉数据库迁移,则可以查看this article

打字稿(可选)

我们可以通过将打字稿,节点类型,TSX和TSUP添加到我们的项目中开始安装Tyspript:

npm i typescript @types/node tsx tsup -D

tsx包将负责在开发环境中运行我们的应用程序,而tsup软件包将是捆绑我们的应用程序的必要条件。

我们现在可以通过以下方式初始化打字稿:

npx tsc --init

这将创建我们的tsconfig.json文件。

在tsconfig文件中,我们可以将目标更改为es2020as es2020 is 100% supported since Node.js 14.0.0)。

{
  "compilerOptions": {
    ...
    "target": "es2020",
    ...
  }
}

为了避免relative import hell,我们还可以在此配置文件中删除baseUrl行并设置自定义paths

{
  "compilerOptions": {
    ...
    "baseUrl": "./",
    "paths": {
      "@/*": ["./src/*"]
    },
    ...
  }
}

这样,而不是必须这样的事情:

import { someModule } from '../../../../../some-file';

我们可以使用这样的绝对导入导入内容:

import { someModule } from '@/custom/path/to/some-file';

好多了,对吗?

要避免始终导入诸如vitest的“测试”和“期望”之类的东西,我们还可以将vitest/globals添加到我们的tsconfig配置文件中:

{
  "compilerOptions": {
    ...
    "types": [
      "vitest/globals"
    ],
    ...
  }
}

,在我们的vitest.config.ts文件中,我们将测试全球指定为true:

export default defineConfig({
  ...
  test: {
    ...
    globals: true,
    ...
  },
  ...
});

现在,您可以从测试文件中删除vitest导入。我们的sample.spec.ts就是这样:

test('should execute', () => {
  expect(1 + 1).toBe(2);
});

我们还可以花点时间添加所需的脚本来运行我们的项目。将它们添加到您的软件包中。JSON文件:

...
"scripts": {
  ...
  "start:dev": "tsx watch src/index.ts", // change the path to match your project initialization file
  "start": "node build/server.js",
  "build": "tsup src --out-dir build",
  ...
}
...

齿轮

好,现在是时候配置我们的测试跑步者了。

我们可以正常安装vitest:

npm i vitest -D

如果您使用的是带有自定义路径的打字稿,也可以安装vite-tsconfig-paths

npm i vite-tsconfig-paths -D

我们现在需要做的是创建我们的vitest配置文件。在项目的根目录中使用以下内容创建一个vitest.config.ts(如果不使用ts,则创建一个vitest.config.ts(vitest.config.js):

import tsconfigPaths from 'vite-tsconfig-paths'; // only if you are using custom tsconfig paths
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {},
  plugins: [tsconfigPaths()],  // only if you are using custom tsconfig paths
});

如果您现在运行npx vitest run,则所有测试都应运行。很好!

如果您还没有任何测试,则可以从创建一个sample.spec.(ts|js)文件开始,该文件仅测试1+1等于2:

import { test, expect } from 'vitest';

test('should execute', () => {
  expect(1 + 1).toBe(2);
});

Tests running

为了避免每次都必须用NPX手动运行vitest,我们可以在项目全局软件包中创建一个新的NPM脚本。让我们在此处添加以下脚本:

...
"scripts": {
  "test": "vitest run",
  "test:watch": "vitest"
},
...

test脚本只能运行一次测试,而test:watch脚本将不断关注文件更改并相应地重新运行测试。

我们还可以添加一个脚本以使用测试覆盖范围报告运行测试。让我们将其添加到我们的软件包。JSON:

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

如果您运行此脚本,它将要求您将@vitest/coverage-c8软件包安装到您的项目中。安装它并使用npm run test:coverage再次运行,您应该在项目中看到测试和新的覆盖范围文件夹。打开Coverage文件夹内的index.html,您应该能够查看有关项目测试覆盖的详细信息。另外,可以将此文件夹安全地添加到您的.gitignore中,因为每次运行测试时都会生成它:coverage命令。

单元和E2E测试

Introduction中所述,单位测试和E2E测试都是软件开发的非常重要的方面。他们确保一切正常工作,并允许我们在用户之前找到错误。

如果您正在执行单元测试,那么拥有内存数据库是完全可以的。由于单元测试旨在孤立地测试代码的单个单位,因此内存数据库可以提供一种快速简便的方法来测试数据库交互而无需依赖真实数据库。这可以加快测试执行时间并简化测试过程。但是,内存数据库不适合端到端测试,因为它们不能准确反映实际数据库的行为。端到端测试旨在从头到尾测试整个系统,包括其与外部系统和依赖关系(例如真实数据库)的交互。内存数据库不提供与真实数据库相同的复杂性和可靠性级别,并且可能无法准确反映生产数据库的行为。因此,我们将配置一个全新的test environment,以使用Vitest进行端到端测试。

创建Prisma测试环境

在我们的root prisma文件夹中,我们将首先创建一个新的vitest-environment-prisma目录。

由于vitest环境需要是一个软件包,因此我们现在可以使用终端打开此目录并在此处运行npm init -y来创建一个新的。

Vitest Environment Prisma

我们需要做的下一件事就是与我们新创建的软件包一起创建一个prisma-test-environment.ts文件。

创建prisma-test-environment.ts文件后,现在我们需要将prisma/vitest-environment-prisma/package.json文件的输入点更改为我们刚刚创建的Prisma-Test-Environment文件:

{
  ...
  "main": "prisma-test-environment.ts",
  ...
}

documentation中所述,自定义的Prisma测试环境看起来像这样:

import type { Environment } from 'vitest';

export default <Environment>{
  name: 'custom',
  setup() {
    // custom setup
    return {
      teardown() {
        // called after all tests with this env have been run
      }
    }
  }
}

这正是我们将在prisma-test-environment.ts文件中使用的结构:

import type { Environment } from 'vitest';

export default <Environment>{
  name: 'prisma',
  setup() {
    // custom setup
    return {
      teardown() {
        // called after all tests with this env have been run
      }
    }
  }
}

唯一的区别是环境的名称需要设置为“ prisma”。

由于我们需要每个测试套件的新数据库,因此我们需要做一些事情。我们在这里要使用的方法是:

对于每个测试套件,我们:

  1. 初始化Prisma客户端;
  2. 生成带有随机名称的新数据库架构(PostgreSQL使用“ public”为默认值);
  3. 更新我们的.env文件中定义的数据库;
  4. 执行Prisma迁移;

在每个测试套件的结尾,我们:

  1. 丢弃新创建的架构;
  2. 与Prisma断开连接;

让我们从初始化Prisma客户端开始:

import type { Environment } from 'vitest';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export default <Environment>{
  ...
}

现在,在我们的setup函数中,我们需要生成我们的随机架构名称:

import type { Environment } from 'vitest';
import { PrismaClient } from '@prisma/client';
import { randomUUID } from 'node:crypto';

const prisma = new PrismaClient();

export default <Environment>{
  name: 'prisma',
  async setup() {
    const schema = randomUUID();

    return {
      async teardown() {
        ...
      },
    }
  },
}

我正在使用Node:Crypto在此处生成随机的UUID,但请随时使用任何生成随机,唯一字符串的工具。

如果您没记错的话,我们的数据库_url遵循一个看起来像这样的结构:

DATABASE_URL="postgresql://docker:docker@localhost:5432/node-test-env?schema=public"

我们唯一要做的就是用新生成的模式名称替换“公共”模式。

让我们创建一个函数以使用新的架构重新生成我们的DATABASE_URL

import 'dotenv/config';
import { randomUUID } from 'node:crypto';
import { Environment } from 'vitest';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

function generateDatabaseURL(schema: string) {
  if (!process.env.DATABASE_URL) {
    throw new Error('Please provide a DATABASE_URL environment variable.');
  }

  const url = new URL(process.env.DATABASE_URL);

  url.searchParams.set('schema', schema);

  return url.toString();
}

export default <Environment>{
  name: 'prisma',
  async setup() {
    const schema = randomUUID();
    const databaseURL = generateDatabaseURL(schema);

    process.env.DATABASE_URL = databaseURL;

    return {
      async teardown() {
        ...
      },
    }
  },
}

另外,请确保安装dotenv软件包:

npm i dotenv

现在,我们只需要用Prisma执行迁移:

import 'dotenv/config';
import { randomUUID } from 'node:crypto';
import { execSync } from 'node:child_process';
import { Environment } from 'vitest';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient()

function generateDatabaseURL(schema: string) {
  ...
}

export default <Environment>{
  name: 'prisma',
  async setup() {
    const schema = randomUUID();
    const databaseURL = generateDatabaseURL(schema);

    process.env.DATABASE_URL = databaseURL;

    execSync('npx prisma migrate deploy');

    return {
      async teardown() {
        ...
      },
    }
  },
}

在这里,我们使用的是npx prisma migrate deploy而不是npx prisma migrate dev,因为我们不想检查schema.prisma文件中的任何更改(因此,创建一个新的迁移)。我们只想执行当前的迁移,这正是npx prisma migrate deploy所做的。

对。现在,唯一剩下的就是我们的拆卸功能。这将在每个测试套件的结尾执行。如上面列出的步骤中所述,我们需要放弃模式并与Prisma断开连接。所以这正是我们要做的:

...
export default <Environment>{
  name: 'prisma',
  async setup() {
    const schema = randomUUID();

    ...

    return {
      async teardown() {
        await prisma.$executeRawUnsafe(
          `DROP SCHEMA IF EXISTS "${schema}" CASCADE`,
        );
        await prisma.$disconnect();
      },
    }
  },
}

如果存在架构并与Prisma客户端断开连接。

我们的Prisma测试环境文件已完成!

告诉vitest何时执行单位或E2E测试

当前,如果我们运行先前创建的testtest:watch脚本,则所有测试将运行,包括单位和E2E测试。这不是很好,因为正如我们之前看到的,单位和端到端测试是完全不同的测试类型。因此,我们现在将创建两种不同的vitest配置:一种用于单元测试,一个用于E2E测试。

为此,我们可以首先与我们的vitest.config.ts一起创建两个文件:

vitest.e2e.config.tsvitest.unit.config.ts

在我们的vitest.unit.config.ts

import { configDefaults, defineConfig, mergeConfig } from 'vitest/config';
import vitestConfig from './vitest.config';

export default mergeConfig(
  vitestConfig,
  defineConfig({
    test: {
      exclude: [
        ...configDefaults.exclude,
        '**/*.e2e-{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
      ],
    },
  }),
);

在这里,我们将旧的vitest.config.ts文件与我们的通用配置合并(例如tsconfigpaths插件)。这样,我们不必两次定义所有内容。

在我们的test对象中,我们告诉Vitest,我们不运行像*.e2e-{test|spec}.*那样命名的任何测试文件。这将确保使用此配置文件时未运行端到端测试。

在我们的vitest.e2e.config.ts中,我们将做类似的事情:

import { defineConfig, mergeConfig } from 'vitest/config';
import vitestConfig from './vitest.config';

export default mergeConfig(
  vitestConfig,
  defineConfig({
    test: {
      include: ['**/*.e2e-{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
      environmentMatchGlobs: [['src/**', 'prisma']],
    },
  }),
);

就像上一个文件一样,我们合并了Common Vitest Config。在此文件中,我们不包括“*.e2e- {test | spec}。*”文件,我们包括它们。这样,我们只执行特定的E2E测试文件。

另外,我们添加了一个新的environmentMatchGlobs对象,该对象告诉Vitest,对于src文件夹中的每个文件,我们都想使用我们先前创建的prisma测试环境。

现在剩下的唯一的是创建我们的测试脚本。

在项目软件包中。

首先,让我们用-c标志指定已经创建的测试命令的配置文件:

"scripts": {
  ...
  "test": "vitest run -c vitest.unit.config.ts",
  "test:watch": "vitest -c vitest.unit.config.ts",
  ...
}

这将告诉vitest,我们想使用刚刚创建为配置文件的vitest.unit.config.ts文件。

现在,我们可以添加用于执行端到端测试的新命令:

"scripts": {
  ...
  "test:e2e": "vitest run -c vitest.e2e.config.ts",
  "test:e2e:watch": "vitest -c vitest.e2e.config.ts",
  ...
}

这是同一件事,但是我们将使用我们的vitest.e2e.config.ts文件,而不是使用vitest.unit.config.ts作为配置文件。这将确保在执行此脚本时仅运行E2E测试。

链接测试环境

由于每个vitest自定义测试环境总是需要是一个软件包,因此我们需要将我们的项目链接到我们创建的Prisma测试环境,否则将不会被识别,并且我们的端到端测试将无法正常运行。为此,我们可以在执行端到端测试之前添加一些脚本以运行。在我们的package.json中,我们将添加以下脚本:

"scripts": {
  ...
  "test:create-prisma-environment": "npm link ./prisma/vitest-environment-prisma",
  "test:install-prisma-environment": "npm link vitest-environment-prisma",
  "pretest:e2e": "run-s test:create-prisma-environment test:install-prisma-environment",
  ...
}
  • test:create-prisma-environment命令将创建Prisma环境。在这里,我们使用NPM链接,该链接在程序包和通常安装的位置之间创建符号链接;
  • test:install-prisma-environment命令将在我们的项目中安装Prisma环境;
  • 在我们运行npm run test:e2e之前,pretest:e2e命令将始终运行。该命令同时运行test:create-prisma-environmenttest:install-prisma-environment。它使用npm-run-all软件包中的run-s,因此请确保以:
npm i npm-run-all -D

从现在开始,每次您使用“ E2E-Spec”或“ E2E检验”的文件以其名称并运行npm run test:e2enpm run test:e2e:watch时,它将包含在Vitest运行的“测试”中,并将执行,确保您的项目中的所有内容都起作用。

结论

在本文中,我们设法将整个Node.js环境配置为Typescript,PostgreSQL,Prisma和Vitest。我们还了解了关于单位和端到端测试以及它们如何不同的信息。

到目前

随时建议改进和/或更正ð。

可以找到本文的完整代码here