244 lines
5.9 KiB
TypeScript
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(),
|
|
}
|
|
}
|