@nuxt/test-utils
An end-to-end suite with @nuxt/test-utils builds the app and boots it in a process of its own, which reaches the test database only through a URL. Serve it over the socket, create it before setup(), and hand setup() its variables:
import { afterAll, describe, expect, it } from 'vitest'
import { $fetch, setup } from '@nuxt/test-utils/e2e'
import { createTestDatabase } from 'nuxt-pglite/testing'
describe('items', async () => {
// Before `setup()`, which reads `env` when it is called.
const db = await createTestDatabase({
config: 'server/pglite.config',
socket: true,
exportEnv: false,
})
// Registered before `setup()`'s own, so it runs after the app is stopped.
afterAll(() => db.close())
await setup({ env: db.env })
it('reads the rows the test wrote', async () => {
await db.pg.query("INSERT INTO items (name) VALUES ('written by the test')")
expect(await $fetch('/api/items')).toEqual([{ name: 'written by the test' }])
})
})
The test writes through db.pg, in its own process, and the app reads the same rows through the socket.
Which apps it fits
The app's database code has to read the URL: the PGlite in dev, Postgres in prod setup, with the server side disabled and the development socket on. A route like this one reaches the test database unchanged:
import pg from 'pg'
export default defineEventHandler(async () => {
const client = new pg.Client({ connectionString: process.env.DATABASE_URL })
await client.connect()
try {
const { rows } = await client.query('SELECT name FROM items ORDER BY id')
return rows
} finally {
await client.end()
}
})
With the server side enabled, usePGlite() in the built app creates its own instance from the config, not the test one.
The order of things
- Create before
setup():setup()builds and starts the app with theenvit is given, so the URL has to exist by then. exportEnv: false: the variables reach the app throughenvalone, and the test process'sprocess.envstays as it was.db.envholds them either way.afterAllbeforesetup(): vitest runsafterAllhooks in the reverse order of their registration (its defaultsequence.hooks), so the database is closed aftersetup()'s own hook has stopped the app, and the app never loses its database while still running.
Between tests
The app is started once per suite, behind one URL, so forking per test does not reach it. Reset the tables the tests write to instead:
import { beforeEach } from 'vitest'
beforeEach(() => db.pg.exec('TRUNCATE items RESTART IDENTITY'))