8. Testing your app
Status: Runnable. Everything on this page works today.
@avijs/testing starts your app on a random local port and gives you a small client for it. Tests talk to the app over real HTTP, so they exercise routing, middleware, guards, validation and serialization exactly as production does.
A first test
This uses Node's built-in test runner on the compiled output, which is what avi new sets up. Any runner works the same way.
import assert from 'node:assert/strict';
import { after, before, test } from 'node:test';
import { createTestClient, type TestClient } from '@avijs/testing';
import { AppModule } from '../app.module.js';
let client: TestClient;
before(async () => {
client = await createTestClient(AppModule);
});
after(() => client.close());
test('creates a user', async () => {
const res = await client.post('/users', { name: 'Grace' });
assert.equal(res.status, 201);
assert.deepEqual(res.body, { id: 2, name: 'Grace' });
});The client has get, post, put, patch, delete and a general request(method, path, init):
| Option | Meaning |
|---|---|
body | Objects are sent as JSON; strings and bytes as they are |
headers | Request headers |
query | { tag: ['a', 'b'] } becomes ?tag=a&tag=b |
Each response has status, headers, text, and body: parsed JSON for JSON responses, the text otherwise. client.close() stops the app and disposes its providers.
Replacing providers
Pass overrides to swap a provider anywhere in the module graph, however deeply it is imported:
const sent: string[] = [];
client = await createTestClient(AppModule, {
overrides: [{ provide: Mailer, useValue: { send: (to: string) => sent.push(to) } }],
});An override that matches no provider is an error, so a renamed token cannot silently stop a test from working. Overrides use the normal provider forms, so useClass and useFactory work too.
createTestClient also accepts any createAvi option, or an app you built yourself:
client = await createTestClient(createAvi({ middleware: [requestId] }).register(AppModule));Testing providers directly
For unit tests of a single service, skip HTTP:
const app = createAvi({ overrides: [{ provide: Db, useValue: fakeDb }] }).register(UsersModule);
await app.init();
const users = app.get(UsersService);With Vitest
Vitest's built-in transform does not compile standard decorators yet. If your app uses decorators, add the small tsc plugin from Chapter 4, or run your tests on the compiled output as above.