SQLite3+Preisma 환경에서 Vitest로 Unit Test 쓰기

스택

  • SQLite 3: DB의 테스트이지만 준비 환경이 번거롭기 때문에 파일 기반 이동을 사용합니다.
  • Prisma : Node.js의 ORM 라이브러리를 사용하여 Unit test를 진행하면 어떻게 되는지 확인합니다.yarn prisma studio에서 DB를 확인할 수 있다는 것은 정말 드문 일이다.
  • Vitest: Jest 호환 API로 작성할 수 있는 unit test framework입니다.실행이 빠르다.이번에는 모듈을 사용하지 않고 실제 투입 데이터의 테스트를 진행한다.
  • 초기 설정


    TypeScript 스크립트를 실행할 수 있는 환경을 만듭니다.실행 중에는 ts-node 대신 esbuild-register를 사용하여 더욱 빨리 실행할 수 있다.
    우선 테스트를 포함하지 않고 강좌 단계를 간단하게 실현한다.
    https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/relational-databases-typescript-postgres
    $ mkdir prisma-sqlite3
    $ cd prisma-sqlite3
    $ yarn init -y
    $ yarn add -D prisma typescript esbuild esbuild-register @types/node
    $ touch index.ts
    
    package.jsonscripts는 다음과 같다.
    package.json
      "scripts": {
        "start": "node -r esbuild-register index.ts",
      },
    
    tsconfig.json도 설정했다.교과서와 같다.
    tsconfig.json
    {
      "compilerOptions": {
        "sourceMap": true,
        "outDir": "dist",
        "strict": true,
        "lib": ["esnext"],
        "esModuleInterop": true
      }
    }
    
    prisma의 초기 설정 명령을 실행합니다.prisma 디렉터리를 만들고 설정 파일을 부하에 설정합니다.
    $ yarn prisma init
    
    생성된 설정 파일을 SQLite3으로 덮어씁니다.SQLite3의 데이터 파일에 대한 경로가 정의되었으나 실제로 작성할 필요는 없습니다.
    .env
    DATABASE_URL="file:./dev.db"
    
    prisma/schema.prisma
    generator client {
      provider = "prisma-client-js"
    }
    
    datasource db {
      provider = "sqlite"
      url      = env("DATABASE_URL")
    }
    
    model Post {
      id        Int      @id @default(autoincrement())
      createdAt DateTime @default(now())
      updatedAt DateTime @updatedAt
      // SQLite では @db.VarChar(255) はサポートされていないので外す
      title     String
      content   String?
      published Boolean  @default(false)
      author    User     @relation(fields: [authorId], references: [id])
      authorId  Int
    }
    
    model Profile {
      id     Int     @id @default(autoincrement())
      bio    String?
      user   User    @relation(fields: [userId], references: [id])
      userId Int     @unique
    }
    
    model User {
      id      Int      @id @default(autoincrement())
      email   String   @unique
      name    String?
      posts   Post[]
      profile Profile?
    }
    
    scheme의 정의가 완료되면 다음 절차를 수행하여 DB에서 표를 작성합니다.
    $ yarn prisma migrate dev --name init
    
    yarn prisma studio를 실행할 때 브라우저에서 만든 테이블을 확인할 수 있습니다.

    실행 확인


    스크립트를 기술하기 위해 데이터를 처리하는 Proisma 클라이언트 라이브러리를 추가합니다.
    $ yarn add @prisma/client
    
    index.ts
    import { PrismaClient } from "@prisma/client";
    
    const prisma = new PrismaClient();
    
    async function main() {
      await prisma.user.create({
        data: {
          name: "Alice",
          email: "[email protected]",
          posts: {
            create: { title: "Hello World" },
          },
          profile: {
            create: { bio: "I like turtles" },
          },
        },
      });
    
      const allUsers = await prisma.user.findMany({
        include: {
          posts: true,
          profile: true,
        },
      });
      console.dir(allUsers, { depth: null });
    }
    
    main()
      .catch((e) => {
        throw e;
      })
      .finally(async () => {
        await prisma.$disconnect();
      });
    
    일단 실행해 보자.yarn start에서 데이터 투입 결과를 나타낼 수 있다.

    시험을 쓰다


    다음은 본론.DB의 unit test를 정의합니다.덮어쓰기c8도 가져왔기 때문에 가져옵니다.
    $ yarn add -D vitest c8
    $ touch index.test.ts
    
    테스트를 쓰기 전에package.json 테스트 실행에 사용할 스크립트를 미리 기술합니다.이번에는 실제 DB에 투입되기 때문에 테스트 수행 전 리셋 테이블prisma migrate reset -f을 실행한다.
    package.json
      "scripts": {
        "start": "node -r esbuild-register index.ts",
        "test": "vitest run",
        "coverage": "vitest run --coverage",
        "db:reset": "prisma migrate reset -f",
        "db:test": "yarn db:reset && yarn test",
        "db:test:coverage": "yarn db:reset && yarn coverage"
      },
    
    index.test.ts 쓰기 테스트.이럴 때는 잠시 우직해도 되기 때문에 먼저 통과한다.이후 함수화 등을 진행한다.
    index.test.ts
    import { test, expect } from "vitest";
    import { PrismaClient } from "@prisma/client";
    
    const prisma = new PrismaClient();
    
    test("User does not exist in the initial DB", async () => {
      const users = await prisma.user.findMany({
        include: {
          posts: true,
          profile: true,
        },
      });
      expect(users).toEqual([]);
    });
    
    테스트를 기술하고 실행하다.실패할 때 표의 상태를 확인합니다.
    $ yarn db:test
    
    성공하면 함수로 잘라냅니다.
    index.test.ts
    export async function findAllUser() {
      const users = await prisma.user.findMany({
        include: {
          posts: true,
          profile: true,
        },
      });
    
      return users;
    }
    
    test("User does not exist in the initial DB", async () => {
      const users = await findAllUser();
      expect(users).toEqual([]);
    });
    
    최종적으로 index.ts로 이동하여 읽습니다.그리고 이것을 반복해서 테스트 모드를 추가합니다.
    index.test.ts
    import { findAllUser } from "./index";
    
    test("User does not exist in the initial DB", async () => {
      const users = await findAllUser();
      expect(users).toEqual([]);
    });
    

    테스트 용례 증가

    index.ts에서 기본 CRUD 모드를 구현합니다.다음에 deleteUserById()가 실패합니다.
    index.ts
    import { PrismaClient, Prisma } from "@prisma/client";
    
    const prisma = new PrismaClient();
    
    export async function createUser(user: Prisma.UserCreateInput) {
      await prisma.user.create({ data: user });
    }
    
    export async function findAllUser() {
      const user = await prisma.user.findMany({
        include: {
          posts: true,
          profile: true,
        },
      });
    
      return user;
    }
    
    export async function findUserById(id: number) {
      const user = await prisma.user.findFirst({
        where: { id },
        include: {
          posts: true,
          profile: true,
        },
      });
    
      return user;
    }
    
    export async function updateUserById(id: number, user: Prisma.UserUpdateInput) {
      await prisma.user.update({
        where: { id },
        data: user,
      });
    }
    
    export async function deleteUserById(id: number) {
      await prisma.user.delete({ where: { id } });
    }
    
    export async function disconnectDB() {
      await prisma.$disconnect();
    }
    
    테스트도 index.test.ts에 쓰십시오.
    index.test.ts
    import { describe, expect, test, afterAll } from "vitest";
    import { Prisma } from "@prisma/client";
    import {
      createUser,
      deleteUserById,
      disconnectDB,
      findAllUser,
      findUserById,
      updateUserById,
    } from "./index";
    
    const demoUser: Prisma.UserCreateInput = {
      name: "Alice",
      email: "[email protected]",
      posts: {
        create: { title: "Hello World" },
      },
      profile: {
        create: { bio: "I like turtles" },
      },
    };
    
    afterAll(async () => {
      await disconnectDB();
    });
    
    test("User does not exist in the initial DB", async () => {
      const users = await findAllUser();
      expect(users).toEqual([]);
    });
    
    describe("CRUD user", async () => {
      test("create user", async () => {
        await createUser(demoUser);
    
        const user = await findUserById(1);
        expect(user?.name).toBe("Alice");
        expect(user?.email).toBe("[email protected]");
        expect(user?.posts[0].title).toBe("Hello World");
        expect(user?.posts[0].published).toBe(false);
        expect(user?.profile?.bio).toBe("I like turtles");
      });
    
      test("update user", async () => {
        await updateUserById(1, {
          name: "Emma",
          email: "[email protected]",
        });
    
        const user = await findUserById(1);
        expect(user?.name).toBe("Emma");
        expect(user?.email).toBe("[email protected]");
      });
    
      test("delete user", async () => {
        await deleteUserById(1);
    
        const user = await findUserById(1);
        expect(user).toBe(null);
      });
    });
    
    yarn db:test에서 실행됩니다.delete user 테스트에 실패했습니다.조사 결과 외부 버튼 제한이 실패한 것으로 밝혀졌다.
    수정 방침은 여러 가지지만 스쉘을 재고하기로 했다.
    prisma/schema.prisma
    model Post {
    -  author    User     @relation(fields: [authorId], references: [id])
    +  author    User     @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
    
    }
    
    model Profile {
    -  user   User    @relation(fields: [userId], references: [id])
    +  user   User    @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
    }
    
    
    업데이트된 Scheme을 DB에 반영합니다.prisma/migrations/ 부하에서 DB 업데이트용 SQL을 생성한 것을 확인할 수 있습니다.
    $ yarn prisma migrate dev --name update
    
    DB의 업데이트yarn db:test를 반영하여 테스트를 실시하면delete user 테스트도 성공했기 때문이다.

    총결산


    나는 유닛 테스트를 직접 쓴 경험이 거의 없기 때문에 이렇게 쓰는 것이 맞는지 모르겠다. 어쨌든 해냈다.
    Preisma Celient 측에서 DROP 테이블의 API를 찾을 수 없기 때문에 테스트 실행 전에 실행prisma migrate reset했습니다. 이보다 더 좋은 방법이 있으면 알려주세요.

    좋은 웹페이지 즐겨찾기