Open2

Bun.js をバックエンドで使いたい

mageomageo

Bun 公式の Docker イメージ oven/bun 上では Prisma が使えないので要注意。Node.js の公式イメージにさらに Bun をインストールして作ったコンテナ内では問題なく使えるのでどうやら Node.js も必要ぽい

FROM node:20.10.0
RUN curl -fsSL https://bun.sh/install | bash
mageomageo

jest-prisma を bun test でもなんとか使えるようにする試み。

x テーブルにメッセージを投稿するための post 関数がありこの関数をテストしたい。

schema.prisma
model X {
  id        Int      @id @default(autoincrement())
  message   String
  createdAt DateTime @default(now())
}
x.ts
import type { PrismaClient } from "@prisma/client";

// prismaClient を引数で受け取ることでテストしやすくしている
export const post = async (params: { prisma: PrismaClient; message: string }) => {
  const { message, prisma } = params;
  return await prisma.x.create({ data: { message } });
};

jest-prisma を使わずに書くと以下のようなテストになる。このテストは実行する度に x テーブルのレコード数が増えていくので2回目以降は失敗してしまう。

x.test.ts
import { PrismaClient } from '@prisma/client';
import { expect, test } from "bun:test";
import { post } from "./x";

test("post", async () => {
  const prisma = new PrismaClient();
  const res = await post({ message: "first post", prisma });
  expect(res.message).toBe("first post");

  const timeline = await prisma.x.findMany();
  expect(timeline.length).toBe(1);
});

ここで jest-prisma を 使ってテスト終了時に db を元の状態に戻したくなる。しかし jest-prisma は Jest の testEnvironment を使っているのでそのままでは bun test には組み込めない。

そこで以下のような preloader を書いて無理やり組み込む。

preload.ts
import type { EnvironmentContext, JestEnvironmentConfig } from "@jest/environment";
import type { Circus } from "@jest/types";
import type { PrismaClient } from "@prisma/client";
import { JestPrisma, PrismaEnvironmentDelegate } from "@quramy/jest-prisma-core";
import { afterEach, beforeEach } from "bun:test";

declare global {
  // eslint-disable-next-line no-var
  var bunTestPrisma: JestPrisma<PrismaClient>;
}

const delegate = new PrismaEnvironmentDelegate(
  {
    projectConfig: { testEnvironmentOptions: {} },
    globalConfig: { rootDir: "" }
  } as JestEnvironmentConfig,
  { testPath: "" } as EnvironmentContext,
);
global.bunTestPrisma = await delegate.preSetup();

beforeEach(async () => {
  await delegate.handleTestEvent({ name: "test_start" } as Circus.Event);
});

afterEach(async () => {
  await delegate.handleTestEvent({ name: "test_done" } as Circus.Event);
});
bunfig.toml
[test]
preload = "./preload.ts"

この preloader で global 空間に bunTestPrisma が生成されるので、x.test.tsPrismaClient を生で使っていた箇所を bunTestPrisma.client に置き換える。

x.test.ts
-  const prisma = new PrismaClient();
+  const prisma = bunTestPrisma.client;

これで何度実行しても成功するテストになった😃

autoincrement している id は元に戻らず進み続けるので注意 → DBの自動連番がロールバックしても戻らない理由