Testing
Test facades with per-test database isolation, entity fixtures, request mocking, and SQL mocking.
Setup
Tests use node:test runner against compiled output. The global setup enforces UTC timezone.
# Run all tests
yarn test
# Run a single test file
yarn test tests/helpers/array.spec.ts
# Run with debugger
yarn test:debugTesting Facade
TestingFacade wraps a Deepkit app for testing with database isolation and lifecycle hooks.
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { TestingHelpers, makeMockRequest } from '@zyno-io/dk-server-foundation';
// Pass app options (not an app instance) -- the facade creates the app for you
const tf = TestingHelpers.createTestingFacade(
{
db: MyDB,
controllers: [UserController],
providers: [UserService]
},
{
enableDatabase: true,
seedData: async facade => {
await loadEntityFixtures([fixtures.user1, fixtures.admin1]);
}
}
);
TestingHelpers.installStandardHooks(tf);
describe('UserController', () => {
it('should return user', async () => {
const res = await makeMockRequest(tf, 'GET', '/api/users/1', {});
assert.strictEqual(res.statusCode, 200);
});
});createTestingFacade(appOptions, facadeOptions?)
Creates a TestingFacade instance. The first argument is app options (same as CreateAppOptions but with config optional -- defaults to BaseAppConfig). The facade calls createApp() internally with port: 0.
ITestingFacadeOptions
| Option | Type | Default | Description |
|---|---|---|---|
enableDatabase | boolean | false | Create an isolated test database |
enableMigrations | boolean | true (when database enabled) | Run migrations after database creation. Set to false to skip. |
autoSeedData | boolean | false | Run seedData automatically before each test |
databasePrefix | string | 'test' | Prefix for test database names |
onBeforeStart | (facade) => Promise<void> | — | Hook before app starts |
onStart | (facade) => Promise<void> | — | Hook after app starts |
onBeforeStop | (facade) => Promise<void> | — | Hook before app stops |
onStop | (facade) => Promise<void> | — | Hook after app stops |
seedData | (facade) => Promise<void> | — | Seed test data |
defaultTestHeaders | Record<string, string> | — | Default headers for mock requests |
Facade Methods
| Method | Description |
|---|---|
start() | Start the app and create the test database |
stop() | Stop the app and destroy the test database |
createDatabase() | Create the isolated test database |
destroyDatabase() | Drop the test database |
truncateTables() | Truncate all tables |
runMigrations() | Run pending migrations |
resetToSeed() | Truncate tables and re-seed |
Database Isolation
Each test process gets its own database: {prefix}_{timestamp}_{pid}_1. Databases are created on start() and dropped on stop().
Environment variables:
| Variable | Description |
|---|---|
TEST_KEEP_DB | Skip database destruction on stop |
Standard Hooks
installStandardHooks(tf) sets up test lifecycle hooks:
before-- Start the facadeafter-- Stop the facadebeforeEach-- Reset to seed data; install DB rejection hooks if database is disabledafterEach-- Reset timers, restore all mocks, clear SQL mocks
Entity Fixtures
Define reusable test data with automatic date handling:
import { TestingHelpers } from '@zyno-io/dk-server-foundation';
const { defineEntityFixtures, loadEntityFixtures } = TestingHelpers;
const fixtures = defineEntityFixtures(User, {
alice: {
id: 1,
name: 'Alice',
email: 'alice@example.com',
createdAt: '2024-01-01T00:00:00Z' // String dates auto-converted
},
bob: {
id: 2,
name: 'Bob',
email: 'bob@example.com',
createdAt: '2024-01-02T00:00:00Z'
}
});
// Load into database
await loadEntityFixtures([fixtures.alice, fixtures.bob]);defineEntityFixtures(entityClass, data)
- Date fields accept ISO strings (converted to
Dateon load) - Fields with defaults (auto-increment, nullable,
HasDefault) are optional - Returns an object with the same keys, each containing prepared fixture data
prepareEntityFixtures(entityClass, data)
Lower-level function to prepare a single fixture record:
const prepared = TestingHelpers.prepareEntityFixtures(User, {
name: 'Charlie',
email: 'charlie@example.com'
});Mock HTTP Requests
import { makeMockRequest } from '@zyno-io/dk-server-foundation';
// Without custom headers
const res = await makeMockRequest(tf, 'GET', '/api/users', {});
// With custom headers
const res = await makeMockRequest(
tf,
'POST',
'/api/users',
{
Authorization: 'Bearer token123',
'Content-Type': 'application/json'
},
{ name: 'Alice', email: 'alice@example.com' }
);
// Response
assert.strictEqual(res.statusCode, 200);
assert.deepStrictEqual(res.json, { id: 1, name: 'Alice' });Methods: GET, PUT, POST, DELETE.
Default headers from options.defaultTestHeaders are merged with per-request headers.
SQL Mocking
Mock database queries without a real database:
import { SqlTestingHelper } from '@zyno-io/dk-server-foundation';
const sql = new SqlTestingHelper();
// Mock entity data
sql.mockEntity(User, [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]);
// Queries against User will return mocked data
const users = await db.query(User).find();
// [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
// Throws if no mock is set up
// "No mock data found for entity: User"
// Clear all mocks
sql.clearMocks();Uses Deepkit's MemoryDatabaseAdapter internally. Automatically cleared by installStandardHooks.
Configuration
For tests without a database, set MYSQL_MIN_IDLE_CONNECTIONS=0 to avoid connection attempts.
Redis Cleanup
If your tests use Redis-backed features, call disconnectAllRedis() in your after() hook:
import { after } from 'node:test';
import { disconnectAllRedis } from '@zyno-io/dk-server-foundation';
after(async () => {
await tf.stop();
await disconnectAllRedis();
});