Testing

vitest

One database for the whole run, created in a globalSetup file before the workers start, and reached by them through the socket.

vitest runs test files in worker processes, so a database created in one file is not visible to the others. nuxt-pglite/testing/vitest turns createTestDatabase into a globalSetup file instead: one database for the whole run, created in the main process before the workers start, served over the socket and closed once the run ends.

Write the setup file

test/pglite.setup.ts
import { definePGliteGlobalSetup } from 'nuxt-pglite/testing/vitest'

export default definePGliteGlobalSetup({
  config: 'server/pglite.config',
})

definePGliteGlobalSetup takes the options of createTestDatabase, except that the socket cannot be turned off: the workers reach the database through it only. socket is true (a free loopback port) by default, or the socket options.

List it in the vitest config

vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globalSetup: ['test/pglite.setup.ts'],
  },
})

Reach it from the tests

The variables are set on process.env before the workers are spawned, so the workers inherit them: code reading DATABASE_URL reaches the test database unchanged. The database is also provided as pglite, with its url and env:

test/todos.test.ts
import pg from 'pg'
import { inject, it } from 'vitest'

const { url } = inject('pglite')

it('connects', async () => {
  const client = new pg.Client({ connectionString: url })
  await client.connect()
  // ...
  await client.end()
})

inject('pglite') is typed by nuxt-pglite/testing/vitest, as long as the setup file is part of your TypeScript project.

As with createTestDatabase, each variable is only set while still unset, and exportEnv: false sets none: the workers then reach the database through inject('pglite') alone.

One database for the run

Every worker talks to the same database. Tests that write to it should either run one file at a time (fileParallelism: false in the vitest config) or clean up after themselves.

A database per file, forked per test

fork() is a method of the TestDatabase object, which for the global database lives in the main process: the workers only get its URL, so they cannot fork it. When tests need isolation, create the database in the test file instead, and fork it per test:

test/todos.test.ts
import pg from 'pg'
import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from 'vitest'
import { createTestDatabase } from 'nuxt-pglite/testing'
import type { TestDatabase } from 'nuxt-pglite/testing'

let db: TestDatabase
let fork: TestDatabase

beforeAll(async () => {
  db = await createTestDatabase({ config: 'server/pglite.config' }) // `init` runs once, here
})
beforeEach(async () => {
  fork = await db.fork({ socket: true }) // a copy of `db`, with a URL of its own
})
afterEach(() => fork.close())
afterAll(() => db.close())

it('writes through PGlite', async () => {
  await fork.pg.query('INSERT INTO todos (title) VALUES ($1)', ['first'])
  const { rows } = await fork.pg.query('SELECT title FROM todos')
  expect(rows).toEqual([{ title: 'first' }])
})

it('starts from the migrated schema again, over the wire', async () => {
  const client = new pg.Client({ connectionString: fork.url })
  await client.connect()
  try {
    const { rows } = await client.query('SELECT count(*)::int AS total FROM todos')
    expect(rows).toEqual([{ total: 0 }])
  } finally {
    await client.end()
  }
})

Each file builds its database once, init and migrations included, and each test gets a fresh copy, so the files can run in parallel and no test sees another's writes. A fork never sets process.env: hand its url (or its env) to the code under test.

Prefer the shared global database when the tests only read, or when building the database per file costs more than the run can afford; prefer a database per file, forked per test, when tests write and must not depend on each other's order.

Provider variables

Code written for a host's own variables reaches the test database with the same preset as the development socket. With provider: 'netlify', the workers get NETLIFY_DB_URL and NETLIFY_DB_DRIVER=server, so drizzle() from drizzle-orm/netlify-db, called with no connection, uses pg against the socket as it does in nuxt dev:

test/pglite.setup.ts
import { definePGliteGlobalSetup } from 'nuxt-pglite/testing/vitest'

export default definePGliteGlobalSetup({
  config: 'server/pglite.config',
  socket: { provider: 'netlify' },
})

See Netlify for the app side.

Aliases

The setup file runs outside Nuxt, where the app's aliases are not defined. A config file importing through #pglite/migrations (or ~~, or another module's alias) needs them passed as alias, as with createTestDatabase:

test/pglite.setup.ts
import { fileURLToPath } from 'node:url'

import { definePGliteGlobalSetup } from 'nuxt-pglite/testing/vitest'

export default definePGliteGlobalSetup({
  config: 'server/pglite.config',
  alias: {
    '#pglite/migrations': fileURLToPath(import.meta.resolve('nuxt-pglite/migrations')),
  },
})

Or import from nuxt-pglite/migrations in the config file, which resolves in both places.