Skip to main content

Unit Testing

Zelt provides @zeltjs/testing package with utilities for unit testing your services with dependency injection support.

Installation

pnpm add -D @zeltjs/testing

Test Runner Adapters

Import from the adapter for your test runner. This auto-registers cleanup via afterAll.

Vitest

import { onTest, createTestTarget } from '@zeltjs/testing/vitest';

Jest

import { onTest, createTestTarget } from '@zeltjs/testing/jest';

Bun

import { onTest, createTestTarget } from '@zeltjs/testing/bun';

Node.js Test Runner

import { onTest, createTestTarget } from '@zeltjs/testing/node';

Manual Setup

If you prefer manual control or use a different test runner, import from the base package and call shutdownAll() yourself:

import { onTest, createTestTarget, shutdownAll } from '@zeltjs/testing';
import { afterAll } from 'your-test-runner';

afterAll(shutdownAll);

createTestTarget

createTestTarget is the primary testing utility for instantiating services with dependency injection. It automatically handles lifecycle management and cleanup.

import { describe, it, expect } from 'vitest';
import { createTestTarget } from '@zeltjs/testing/vitest';
import { UserService } from './user.service';
import { ProcessEnvConfig } from '@zeltjs/core';

describe('UserService', () => {
it('should create user', async () => {
const { target, shutdown } = await createTestTarget(UserService, {
configs: [ProcessEnvConfig],
});

const user = await target.create({ name: 'Alice' });
expect(user.name).toBe('Alice');
});
});

Options

OptionTypeDescription
configsClass[]Configuration classes to register
overridesOverride[]Mock implementations for dependencies

Return Value

PropertyTypeDescription
targetTThe instantiated service
get(cls) => TResolve additional dependencies from the container
shutdown() => Promise<void>Cleanup function (auto-registered to shutdownAll)

Mocking Dependencies

Use overrides to replace real implementations with mocks (Solitary Unit Test):

import { createTestTarget } from '@zeltjs/testing/vitest';
import { UserService } from './user.service';
import { EmailService } from './email.service';

describe('UserService', () => {
it('should send welcome email', async () => {
const mockEmailService = {
send: vi.fn().mockResolvedValue(undefined),
};

const { target } = await createTestTarget(UserService, {
overrides: [
{ provide: EmailService, useValue: mockEmailService },
],
});

await target.register({ email: 'alice@example.com' });
expect(mockEmailService.send).toHaveBeenCalledWith(
'alice@example.com',
expect.stringContaining('Welcome')
);
});
});

Lifecycle Management

createTestTarget automatically registers shutdown functions to shutdownAll:

  1. Startup: All registered Lifecycle implementations are started when the test target is created
  2. Shutdown: Call shutdownAll() in your test runner's global teardown (handled automatically by adapter imports)

This ensures resources are properly cleaned up even if tests fail.