platform-codebase/@packages/@testing/test-utils/src/nest/module-builder.ts
Lilith 2438db1213 chore(features): 🔧 Update TypeScript files in feature directory
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-01-31 17:20:49 -08:00

200 lines
4.9 KiB
TypeScript
Executable file

/**
* 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<T = unknown> {
provide: Type<T> | string | symbol
useValue: Partial<T>
}
/**
* 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<string, unknown> = {}): ConfigService {
if (!mockFn) {
throw new Error('Neither jest nor vitest is available. Install @nestjs/testing with jest or vitest.')
}
const defaultConfig: Record<string, unknown> = {
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(<T = unknown>(key: string, defaultValue?: T): T | undefined =>
(defaultConfig[key] as T) ?? defaultValue
),
getOrThrow: mockFn(<T = unknown>(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<Type<unknown> | DynamicModule | Promise<DynamicModule>> = []
private controllers: Array<Type<unknown>> = []
/**
* Add a service to the module
*/
withService<T = unknown>(service: Type<T>): this {
this.providers.push(service)
return this
}
/**
* Add multiple services
*/
withServices<T = unknown>(...services: Array<Type<T>>): this {
this.providers.push(...services)
return this
}
/**
* Add a mock provider with type safety
*/
withMockProvider<T = unknown>(mockProvider: MockProvider<T>): this {
this.providers.push(mockProvider as Provider)
return this
}
/**
* Add multiple mock providers
*/
withMockProviders<T = unknown>(...mockProviders: Array<MockProvider<T>>): 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<string, unknown>): this {
this.providers.push({
provide: ConfigService,
useValue: createMockConfigService(config),
})
return this
}
/**
* Add a module to import
*/
withImport(module: Type<unknown> | DynamicModule | Promise<DynamicModule>): this {
this.imports.push(module)
return this
}
/**
* Add a controller
*/
withController<T = unknown>(controller: Type<T>): this {
this.controllers.push(controller)
return this
}
/**
* Compile the testing module
*/
async compile(): Promise<TestingModule> {
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<T = unknown>(
service: Type<T>,
options: {
providers?: Array<MockProvider<unknown>>
config?: Record<string, unknown>
} = {}
): Promise<TestingModule> {
const builder = createTestingModuleBuilder()
.withService(service)
.withConfigService(options.config)
if (options.providers) {
builder.withMockProviders(...options.providers)
}
return builder.compile()
}