Testing
Database testing
Your tests can migrate a database for each test, fill it with factories and seeders, and check the rows your code wrote.
Introduction#
By default, each test runs on a new in-memory SQLite database (see Environment), which is empty: no tables until a test migrates it.
Resetting the database after each test#
RefreshDatabase gives each test a migrated database. Add it to the file's test case:
import { RefreshDatabase } from '@orieljs/framework/testing'import { test } from 'vitest'import { TestCase } from '../TestCase'const t = TestCase.boot({ uses: [RefreshDatabase] })test('the posts page loads', async () => { const response = await t.get('/posts') response.assertOk()})On the in-memory database, it runs your migrations for each test, on that test's new database. On a database in a
file or on a server, it runs migrate:fresh once, and then runs each test inside a transaction that it rolls back
afterwards, so no test sees another's rows.
| Trait | What it does |
|---|---|
RefreshDatabase | Migrates as above |
DatabaseMigrations | Runs migrate:fresh before each test and migrate:rollback after it |
DatabaseTransactions | Runs each test in a transaction that is rolled back, without migrating |
To use a trait in every test, set it on your TestCase:
import type { Application } from '@orieljs/framework'import { RefreshDatabase, TestCase as BaseTestCase } from '@orieljs/framework/testing'import { afterEach, beforeEach } from 'vitest'import { createApplication } from '../bootstrap/app'export class TestCase extends BaseTestCase { static override hooks = { beforeEach, afterEach } override readonly uses = [RefreshDatabase] override createApplication(): Promise<Application> { return createApplication() }}connectionsToTransact lists the connections the transaction traits wrap, the default one unless you name others.
Model factories#
Factories make the rows a test needs:
import { RefreshDatabase } from '@orieljs/framework/testing'import { test } from 'vitest'import { User } from '../../app/Models/User'import { TestCase } from '../TestCase'const t = TestCase.boot({ uses: [RefreshDatabase] })test('models can be instantiated', async () => { const user = await User.factory().create() const response = await t.actingAs(user).get('/dashboard') response.assertOk()})Oriel finds the factories in database/ when your tests start.
Running seeders#
t.runSeeder() runs DatabaseSeeder in a test, or the seeder you name:
await t.runSeeder()await t.runSeeder('OrderStatusSeeder')To seed before every test, set seed, or seeder for another seeder, on your TestCase. RefreshDatabase and
DatabaseMigrations then seed after they migrate:
export class TestCase extends BaseTestCase { override seed = true}Available assertions#
The test case has these assertions. They read the database, so await each one:
await t.assertDatabaseHas('users', { email: 'sally@example.com' })await t.assertModelExists(user)| Assertion | Passes when |
|---|---|
assertDatabaseHas(table, data) | A row of the table has the values; null matches a null column |
assertDatabaseMissing(table, data) | No row has them |
assertDatabaseCount(table, count) | The table has that many rows |
assertDatabaseEmpty(table) | The table has no rows |
assertModelExists(model), assertModelMissing(model) | The model's row is in the database, or isn't |
assertSoftDeleted(tableOrModel, data?), assertNotSoftDeleted(tableOrModel, data?) | The row is soft deleted, or isn't |
table is a table's name, or a model, whose table and connection the assertion uses. assertDatabaseHas(),
assertDatabaseMissing(), assertDatabaseCount() and assertDatabaseEmpty() take a connection name as their last
argument, and assertSoftDeleted() and assertNotSoftDeleted() after the data. A failed assertDatabaseHas() shows a
few rows of the table, to help you see what differs.