Skip to the content
OrielDocs
0.1.0-alpha.10GitHub

Testing

Testing: getting started

Oriel apps are tested with Vitest. A new app has tests ready to run, a TestCase that gives each test a fresh application, and helpers to send requests, run commands and check the database.

Introduction#

tests has two folders: tests/Feature for tests that send requests to your app or run its commands, and tests/Unit for tests of one piece of code on its own. A test file's name ends in .test.ts, or .test.tsx for one that renders TSX.

tests/TestCase.ts is the class your tests boot the app with, and vitest.config.ts sets Vitest up for your app. A test file boots the test case once, at the top, and uses it in each test:

tests/Feature/Example.test.ts
import { test } from 'vitest'import { TestCase } from '../TestCase'const t = TestCase.boot()test('the application returns a successful response', async () => {  const response = await t.get('/')  response.assertOk()})

TestCase.boot() registers Vitest's beforeEach and afterEach hooks: before each test, it creates and boots a new application; after it, it tears that application down. No state survives from one test to the next.

Environment#

Before each application is created, the test case sets these variables, unless they are already set:

VariableValue
APP_ENVtesting
BCRYPT_ROUNDS4
CACHE_STOREarray
DB_CONNECTIONsqlite
DB_DATABASE:memory:
MAIL_MAILERarray
QUEUE_CONNECTIONsync
SESSION_DRIVERarray

So your tests send no mail, run jobs at once, and each test gets its own empty in-memory database. A new app's vitest.config.ts sets the same values as Vitest's env, because Bun reads .env before the tests start. Vitest's env replaces what your shell or your CI sets, so to run your tests with another value, such as a MySQL database, change it in vitest.config.ts.

With APP_ENV set to testing, a .env.testing file, when you add one, is read in place of .env. For the other variables, what you set in your shell or your CI wins over both files.

To change the app before it boots, override defineEnvironment() in your TestCase:

tests/TestCase.ts
import type { Application } from '@orieljs/framework'import { ConfigRepository } from '@orieljs/framework/contracts'import { 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 createApplication(): Promise<Application> {    return createApplication()  }  protected override defineEnvironment(app: Application): void {    app.make(ConfigRepository).set('mail.default', 'array')  }}

Creating tests#

make:test writes a test in tests/Feature, and with --unit in tests/Unit:

Terminal
node oriel make:test UserTestnode oriel make:test UserTest --unit

The file is named after the test without its Test: tests/Feature/User.test.ts. Both write the same Vitest test, with test(), describe() and expect(); for a feature test, import your TestCase and add const t = TestCase.boot(). A unit test that doesn't need the app leaves it out:

tests/Unit/Example.test.ts
import { expect, test } from 'vitest'test('true is true', () => {  expect(true).toBe(true)})

t.app is the current test's application, inside a test: t.app.make(Service) resolves a service from it.

Running tests#

oriel test runs your tests with Vitest:

Terminal
node oriel test

npm test does the same, and npm run test:bun runs them on Bun. Name files or folders to run only those, and --filter runs the tests whose names match:

Terminal
node oriel test tests/Feature/User.test.tsnode oriel test --filter="the application returns"

Anything after -- goes to Vitest as it is, such as node oriel test -- --coverage for a coverage report (with @vitest/coverage-v8 installed).

Running tests in parallel#

Vitest runs your test files at the same time, in several workers. With the default in-memory SQLite database, each test has its own database, so tests can't see each other's rows.

Test traits#

A trait adds setup to the tests of a file. RefreshDatabase gives each test a migrated database:

tests/Feature/Posts.test.ts
import { RefreshDatabase } from '@orieljs/framework/testing'import { test } from 'vitest'import { TestCase } from '../TestCase'const t = TestCase.boot({ uses: [RefreshDatabase] })test('posts can be listed', async () => {  ;(await t.get('/posts')).assertOk()})

Database testing covers it and the other database traits.

What the test case gives you#

HelperPage
t.get(), t.post(), t.actingAs() and the response's assertionsHTTP tests
t.artisan()Testing commands
Live.test()Live tests
Mail.fake(), Queue.fake() and the other fakesFakes
t.assertDatabaseHas() and the database traitsDatabase testing
t.travelTo(), t.freezeTime()Time travel

While your tests run, the app runs as a console app in the testing environment: CSRF tokens aren't checked, so your tests can post forms without one.

Privacy policy ·

esc