platform-codebase/@packages/@testing/test-utils/src/nest/mock-helpers.ts
2026-01-14 10:48:32 -08:00

244 lines
5.9 KiB
TypeScript

/**
* NestJS Mock Helpers
*
* Helper functions for creating mock implementations of common NestJS patterns.
* Compatible with both Jest (NestJS default) and Vitest.
*/
import { type Repository, type ObjectLiteral } from 'typeorm'
/**
* 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 TypeORM repository with common methods
*
* @example
* // Jest
* const repo = createMockRepository<User>()
*
* // Vitest
* const repo = createMockRepository<User>()
*/
export function createMockRepository<T extends ObjectLiteral>(): Repository<T> {
if (!mockFn) {
throw new Error('Neither jest nor vitest is available. Install @nestjs/testing with jest or vitest.')
}
return {
find: mockFn(),
findOne: mockFn(),
findOneBy: mockFn(),
findAndCount: mockFn(),
save: mockFn(),
create: mockFn(),
update: mockFn(),
delete: mockFn(),
remove: mockFn(),
count: mockFn(),
createQueryBuilder: mockFn(() => ({
where: mockFn().mockReturnThis(),
andWhere: mockFn().mockReturnThis(),
orWhere: mockFn().mockReturnThis(),
orderBy: mockFn().mockReturnThis(),
skip: mockFn().mockReturnThis(),
take: mockFn().mockReturnThis(),
leftJoin: mockFn().mockReturnThis(),
leftJoinAndSelect: mockFn().mockReturnThis(),
getOne: mockFn(),
getMany: mockFn(),
getManyAndCount: mockFn(),
execute: mockFn(),
})),
} as unknown as Repository<T>
}
/**
* Create a mock for a service with specified methods
*
* @example
* const mockUsersService = createMockService<UsersService>({
* findByEmail: vi.fn(),
* create: vi.fn(),
* })
*/
export function createMockService<T>(methods: Partial<T>): T {
return methods as T
}
/**
* Base user interface for mock factory
*/
export interface MockUser {
id: string
email: string
username: string
passwordHash: string
role: string
createdAt: Date
updatedAt: Date
}
/**
* Base session interface for mock factory
*/
export interface MockSession {
id: string
userId: string
token: string
expiresAt: Date
createdAt: Date
}
/**
* Base request interface for mock factory
*/
export interface MockRequest extends Record<string, unknown> {
headers: Record<string, unknown>
body: Record<string, unknown>
query: Record<string, unknown>
params: Record<string, unknown>
user?: unknown
}
/**
* Base response interface for mock factory
*/
export interface MockResponse extends Record<string, unknown> {
status: ReturnType<typeof mockFn>
json: ReturnType<typeof mockFn>
send: ReturnType<typeof mockFn>
cookie: ReturnType<typeof mockFn>
clearCookie: ReturnType<typeof mockFn>
redirect: ReturnType<typeof mockFn>
}
/**
* Common mock data factories
*/
export const mockFactories = {
/**
* Create a mock user object with type-safe overrides
*
* @example
* const user = mockFactories.user({ email: 'custom@example.com' })
* // Type inference: user has all MockUser properties
*/
user: <T extends Partial<MockUser> = Record<string, never>>(overrides?: T): MockUser & T => ({
id: 'user-123',
email: 'test@example.com',
username: 'testuser',
passwordHash: '$2b$10$hashedpassword',
role: 'user',
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-01'),
...overrides,
} as MockUser & T),
/**
* Create a mock session object with type-safe overrides
*
* @example
* const session = mockFactories.session({ token: 'custom-token' })
*/
session: <T extends Partial<MockSession> = Record<string, never>>(overrides?: T): MockSession & T => ({
id: 'session-123',
userId: 'user-123',
token: 'session-token',
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
createdAt: new Date(),
...overrides,
} as MockSession & T),
/**
* Create a mock request object with type-safe overrides
*
* @example
* const req = mockFactories.request({ body: { data: 'test' } })
*/
request: <T extends Partial<MockRequest> = Record<string, never>>(overrides?: T): MockRequest & T => ({
headers: {},
body: {},
query: {},
params: {},
user: undefined,
...overrides,
} as MockRequest & T),
/**
* Create a mock response object with chainable methods
*/
response: (): MockResponse => {
if (!mockFn) {
throw new Error('Neither jest nor vitest is available.')
}
return {
status: mockFn().mockReturnThis(),
json: mockFn().mockReturnThis(),
send: mockFn().mockReturnThis(),
cookie: mockFn().mockReturnThis(),
clearCookie: mockFn().mockReturnThis(),
redirect: mockFn().mockReturnThis(),
}
},
}
/**
* Create a mock logger for NestJS services
*/
export function createMockLogger() {
if (!mockFn) {
throw new Error('Neither jest nor vitest is available.')
}
return {
log: mockFn(),
error: mockFn(),
warn: mockFn(),
debug: mockFn(),
verbose: mockFn(),
}
}
/**
* Create a mock HTTP exception filter context
*
* @example
* const context = createMockExecutionContext(
* mockFactories.request({ user: { id: '123' } }),
* mockFactories.response()
* )
*/
export function createMockExecutionContext<
TRequest extends Record<string, unknown> = MockRequest,
TResponse extends Record<string, unknown> = MockResponse,
>(request: TRequest = {} as TRequest, response: TResponse = {} as TResponse) {
if (!mockFn) {
throw new Error('Neither jest nor vitest is available.')
}
return {
switchToHttp: () => ({
getRequest: () => request,
getResponse: () => response,
}),
getClass: mockFn(),
getHandler: mockFn(),
getArgs: mockFn(),
getArgByIndex: mockFn(),
switchToRpc: mockFn(),
switchToWs: mockFn(),
getType: mockFn(),
}
}