Testing
Run the application around each test, on a new database, as the caller of your choice.
testApplication(app) starts the application before each test of the file
and stops it after: on a new in-memory database, and an HTTP server on a
free port. Tests run with Vitest; install
@ultimabase/testing as a dev dependency:
npm install -D @ultimabase/testing vitest// Starts the application before each test, on a new in-memory database and
// a free port, and stops it after.
import { asUser, testApplication } from '@ultimabase/testing'
import { master } from 'ultimabase'
import { expect, it } from 'vitest'
import { app } from './app.ts'
import { Note } from './models.ts'
const running = testApplication(app)The full example, with its tests: examples/testing.
The application starts as it is declared, its plugins and functions included, but without its info lines: warnings and errors still print.
A new database each test
Each test starts on an empty database: a row one test writes is gone in the next, so tests run in any order.
it('writes a note', async () => {
await Note.create({ data: { text: 'Milk', ownerId: 'ada' } }, { as: master })
expect(await Note.count({}, { as: master })).toBe(1)
})
it('starts on an empty database', async () => {
expect(await Note.count({}, { as: master })).toBe(0)
})As a signed-in user
asUser({ id }) is the caller a session gives, without signing in: its
access rules apply, as in the application.
// A caller as a session gives it, without signing in.
const ada = { as: asUser({ id: 'ada' }) }
const note = await Note.create({ data: { text: 'Milk' } }, ada)
expect(note.ownerId).toBe('ada')
const grace = { as: asUser({ id: 'grace' }) }
expect(await Note.findMany({}, grace)).toEqual([]){ as: master } skips the rules, to seed rows or read them all. Without
as, the caller is anonymous.
Over HTTP
running.url is the server's address during a test, such as
http://localhost:54321: a test calls the API as a client does.
await Note.create({ data: { text: 'Milk', ownerId: 'ada' } }, { as: master })
// `running.url` is the server's address during the test.
const response = await fetch(`${running.url}/graphql`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ query: '{ notes { edges { node { text } } } }' }),
})
// Without a session, the caller is anonymous: no note is its own.
expect(await response.json()).toEqual({ data: { notes: { edges: [] } } })On PostgreSQL
{ database: 'declared' } runs the tests on the application's own
database: on postgres(), in a schema of the test file's own, named
ultimabase_test_<random>. The first test creates the tables in it, each
later one starts with them emptied, and the schema is dropped after the
file's last test. No other table is read or written.
// On the database DATABASE_URL names: the file's own schema, its tables
// emptied before each test, dropped after the last.
testApplication(app, { database: 'declared' })
it('deletes the tasks with their project', async () => {
const project = await Project.create(
{ data: { name: 'Launch', tasks: { create: [{ title: 'Write' }] } } },
{ as: master },
)
await Project.delete({ where: { id: project.id } }, { as: master })
expect(await Task.count({}, { as: master })).toBe(0)
})
it('starts with empty tables', async () => {
expect(await Project.count({}, { as: master })).toBe(0)
})A test on PostgreSQL checks what memory() cannot: raw SQL, a
constraint, the real column types. It is slower: keep most tests on
memory(), and name these *.postgres.test.ts to run them apart.
Reference
testApplication(app, options?)
| Option | Type | Default | Effect |
|---|---|---|---|
database | 'declared' | memory() | The application's own database, in the file's own schema. |
It returns { url }: the HTTP server's address, read during a test.
asUser(user)
| Argument | Type | Returns |
|---|---|---|
user | { id }, or the registered user row | a caller of kind 'user', for { as } |
Errors
| Message | When |
|---|---|
testApplication takes the value defineApp returned. | Another value was given. |
url is the address of httpPlugin(), read during a test: add httpPlugin() to the application. | url read outside a test, or without HTTP. |
testApplication runs on the declared database when it gives a test file tables of its own, as postgres() does; this one does not: remove { database: 'declared' }. | The database cannot give a schema per file. |
Limits
testApplicationadds Vitest'sbeforeEachandafterEach: call it at the top of a file or adescribe, never inside a test.- A process killed during a test file leaves its schema behind.