bun:sqlite を使って bun test で Hono + Cloudflare Workers + D1 をテストする

🔥
note

Cloudflare D1 を bun:sqlite で代用することで、 wrangler を使わずに bun test でテストが書けるようにした。ORM も使っていないような簡単なアプリ向け。

まず、 bun:sqlite をオンメモリで作成して、必要なテーブルを用意しつつ D1 互換のインターフェイスを返すモックを作れるようにする。

import { Database } from 'bun:sqlite';

export function createMockD1Database() {
    const db = new Database(':memory:');

    db.exec('BEGIN');
    db.exec(`
        CREATE TABLE ...
    `);
    db.exec('COMMIT');

    return {
        prepare: (query: string) => {
            const stmt = db.prepare(query);

            return {
                bind: (...params: any[]) => ({
                    all: async () => {
                        try {
                            const results = stmt.all(...params);
                            return {
                                results,
                                success: true,
                                meta: {},
                            };
                        } catch (error: any) {
                            return {
                                results: [],
                                success: false,
                                meta: { error: error.message },
                            };
                        }
                    },
                    first: async () => {
                        try {
                            return stmt.get(...params) || null;
                        } catch (error) {
                            return null;
                        }
                    },
                    run: async () => {
                        try {
                            stmt.run(...params);
                            return {
                                success: true,
                                meta: {
                                    changes: db.changes,
                                    last_row_id: db.lastInsertRowid,
                                },
                            };
                        } catch (error: any) {
                            return {
                                success: false,
                                meta: { error: error.message },
                            };
                        }
                    },
                }),
            };
        },

        batch: async (statements: any[]) => {
            const results = [];
            for (const statement of statements) {
                const result = await statement.run();
                results.push(result);
            }
            return results;
        },

        exec: (sql: string) => {
            return db.exec(sql);
        },
    };
}

このモックを Hono の testClientDB として渡すことで、 D1 の代わりにモックが使用され、 他のツールなしに bun test でテストが書ける。

describe('Tests', () => {
    let client: TestClient;
    let mockDB: ReturnType<typeof createMockD1Database>;

    beforeEach(() => {
        mockDB = createMockD1Database();
        client = testClient(app, {
            DB: mockDB,
        });
    });

    test('api', async () => {
        const res = await client.api.items.$get();
        expect(res.status).toBe(200);

        const items = await res.json();
        expect(Array.isArray(items)).toBe(true);
        expect(items).toHaveLength(0);
    });
})

事前にテストデータなんかがほしいときは普通に mockDB.exec を使って入れたりしている。

Refs

yaakai.to