/** * NestJS Testing Module Utilities * * Helper functions for creating NestJS testing modules with common patterns. */ import { type Type, type DynamicModule, type Provider } from '@nestjs/common' import { ConfigService } from '@nestjs/config' import { Test, type TestingModule } from '@nestjs/testing' /** * Mock provider configuration with type safety */ export interface MockProvider { provide: Type | string | symbol useValue: Partial } /** * Detect whether we're using Jest or Vitest */ const getMockFn = () => { if (typeof (globalThis as any).vi !== 'undefined') { return (globalThis as any).vi.fn } if (typeof (globalThis as any).jest !== 'undefined') { return (globalThis as any).jest.fn } return undefined } const mockFn = getMockFn() /** * Create a mock ConfigService with common configuration * * @example * const configService = createMockConfigService({ * DATABASE_URL: 'postgresql://localhost/test', * CUSTOM_KEY: 'custom-value' * }) */ export function createMockConfigService(config: Record = {}): ConfigService { if (!mockFn) { throw new Error('Neither jest nor vitest is available. Install @nestjs/testing with jest or vitest.') } const defaultConfig: Record = { NODE_ENV: 'test', DATABASE_URL: 'postgresql://test:test@localhost:25432/test', API_URL: 'http://localhost:4000', JWT_SECRET: 'test-jwt-secret', ...config, } return { get: mockFn((key: string, defaultValue?: T): T | undefined => (defaultConfig[key] as T) ?? defaultValue ), getOrThrow: mockFn((key: string): T => { const value = defaultConfig[key] if (value === undefined) { throw new Error(`Configuration key "${key}" does not exist`) } return value as T }), } as unknown as ConfigService } /** * Create a testing module builder with common setup * * @example * const module = await createTestingModuleBuilder() * .withService(AuthService) * .withMockProvider({ * provide: UsersService, * useValue: { findByEmail: vi.fn() } * }) * .compile() */ export class TestingModuleBuilder { private providers: Provider[] = [] private imports: Array | DynamicModule | Promise> = [] private controllers: Array> = [] /** * Add a service to the module */ withService(service: Type): this { this.providers.push(service) return this } /** * Add multiple services */ withServices(...services: Array>): this { this.providers.push(...services) return this } /** * Add a mock provider with type safety */ withMockProvider(mockProvider: MockProvider): this { this.providers.push(mockProvider as Provider) return this } /** * Add multiple mock providers */ withMockProviders(...mockProviders: Array>): this { this.providers.push(...(mockProviders as Provider[])) return this } /** * Add a generic provider (for advanced use cases) */ withProvider(provider: Provider): this { this.providers.push(provider) return this } /** * Add ConfigService with custom config */ withConfigService(config?: Record): this { this.providers.push({ provide: ConfigService, useValue: createMockConfigService(config), }) return this } /** * Add a module to import */ withImport(module: Type | DynamicModule | Promise): this { this.imports.push(module) return this } /** * Add a controller */ withController(controller: Type): this { this.controllers.push(controller) return this } /** * Compile the testing module */ async compile(): Promise { const moduleBuilder = Test.createTestingModule({ providers: this.providers, imports: this.imports, controllers: this.controllers, }) return moduleBuilder.compile() } } /** * Create a new testing module builder */ export function createTestingModuleBuilder(): TestingModuleBuilder { return new TestingModuleBuilder() } /** * Quick helper to create a simple testing module with one service and mocks * * @example * const module = await createSimpleTestingModule(AuthService, { * providers: [ * { provide: UsersService, useValue: { findByEmail: vi.fn() } } * ] * }) */ export async function createSimpleTestingModule( service: Type, options: { providers?: Array> config?: Record } = {} ): Promise { const builder = createTestingModuleBuilder() .withService(service) .withConfigService(options.config) if (options.providers) { builder.withMockProviders(...options.providers) } return builder.compile() }