200 lines
4.9 KiB
TypeScript
Executable file
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()
|
|
}
|