vitest-create-mock
v0.0.6
Published
A utility library for creating mocks in Vitest tests.
Readme
vitest-create-mock
A utility library for creating type-safe, deeply mocked objects in Vitest tests. This is a port of @golevelup/ts-jest's createMock functionality, specifically designed for Vitest.
Features
- 🎯 Type-Safe Mocking - Full TypeScript support with proper type inference
- 🔄 Deep Mocking - Automatically mocks nested properties and methods
- ⚡ Auto-Mocking - Automatically creates mocks for properties not explicitly provided
- 🎭 Partial Mocking - Provide only the properties you need to mock
- 🔒 Strict Mode - Optional strict mode to catch unmocked method calls
- 🧩 Proxy-Based - Efficient proxy-based implementation with caching
- ✨ Promise Support - Automatically handles async/await and promises
Installation
pnpm add -D vitest-create-mocknpm install --save-dev vitest-create-mockyarn add -D vitest-create-mockbun add -d vitest-create-mockQuick Start
import { createMock } from 'vitest-create-mock';
import { describe, expect, it } from 'vitest';
interface UserService {
getUser: (id: number) => Promise<{ name: string; email: string }>;
deleteUser: (id: number) => Promise<void>;
}
describe('UserController', () => {
it('should get user', async () => {
const userService = createMock<UserService>({
getUser: async () => ({ name: 'John', email: '[email protected]' }),
});
const user = await userService.getUser(1);
expect(user.name).toBe('John');
expect(userService.getUser).toHaveBeenCalledWith(1);
});
});API
createMock<T>(partial?, options?)
Creates a deeply mocked object of type T.
Parameters
partial(optional): A partial implementation of the type to mock- Type:
PartialFuncReturn<T> - Default:
{} - Only provide the properties/methods you want to explicitly mock
- Type:
options(optional): Configuration options- Type:
MockOptions - Properties:
name?: string- Name for the mock (useful for debugging), default:'mock'strict?: boolean- Enable strict mode, default:false
- Type:
Returns
A DeepMocked<T> object where all methods are Vitest mocks and all properties are accessible.
Types
DeepMocked<T>
Recursively transforms a type so that all methods become Vitest mocks while preserving type information.
type DeepMocked<T> = {
[K in keyof T]: Required<T>[K] extends (...args: any[]) => infer U
? Mock<Required<T>[K]> & ((...args: Parameters<Required<T>[K]>) => DeepMocked<U>)
: DeepMocked<T[K]>;
} & T;PartialFuncReturn<T>
Allows partial mocking of an object's methods while maintaining type safety.
MockOptions
Configuration options for createMock:
type MockOptions = {
name?: string;
strict?: boolean;
};Usage Examples
Basic Mocking
interface Calculator {
add: (a: number, b: number) => number;
subtract: (a: number, b: number) => number;
}
const calc = createMock<Calculator>({
add: (a, b) => a + b,
});
expect(calc.add(2, 3)).toBe(5);
expect(calc.add).toHaveBeenCalledWith(2, 3);Auto-Mocking
Properties and methods not provided are automatically mocked:
interface Service {
method1: () => string;
method2: () => number;
}
const service = createMock<Service>(); // No partial provided
service.method1(); // Automatically mocked
service.method2(); // Automatically mocked
expect(service.method1).toHaveBeenCalled();
expect(service.method2).toHaveBeenCalled();Deep Nested Mocking
interface ExecutionContext {
switchToHttp: () => {
getRequest: () => Request;
getResponse: () => Response;
};
}
const context = createMock<ExecutionContext>({
switchToHttp: () => ({
getRequest: () => ({ headers: { authorization: 'Bearer token' } }),
}),
});
const request = context.switchToHttp().getRequest();
expect(request.headers.authorization).toBe('Bearer token');Using Mock Methods
Since all methods are Vitest mocks, you can use all Vitest mock features:
interface DataService {
fetchData: () => Promise<string>;
}
const service = createMock<DataService>();
// Mock implementation
service.fetchData.mockResolvedValueOnce('first call');
service.fetchData.mockResolvedValueOnce('second call');
expect(await service.fetchData()).toBe('first call');
expect(await service.fetchData()).toBe('second call');
// Verify calls
expect(service.fetchData).toHaveBeenCalledTimes(2);Mocking Classes
class UserRepository {
findById(id: number): User | null {
// Implementation
}
save(user: User): void {
// Implementation
}
}
const repo = createMock<UserRepository>(undefined, { name: 'UserRepository' });
repo.findById.mockReturnValueOnce({ id: 1, name: 'Alice' });
const user = repo.findById(1);
expect(user.name).toBe('Alice');Strict Mode
Strict mode throws an error when calling methods that haven't been stubbed:
interface Service {
doSomething: (value: string) => boolean;
}
const service = createMock<Service>({}, { strict: true });
// This will throw an error
expect(() => service.doSomething('test')).toThrow(
'Method mock.doSomething was called without being explicitly stubbed'
);
// Stub the method first
service.doSomething.mockReturnValue(true);
expect(service.doSomething('test')).toBe(true); // Now it worksMocking with Optional Properties
interface Config {
apiUrl: string;
timeout?: number;
retries?: number;
}
const config = createMock<Config>({
apiUrl: 'https://api.example.com',
timeout: undefined, // Explicitly set optional property
});
expect(config.apiUrl).toBe('https://api.example.com');
expect(config.timeout).toBeUndefined();Property Assignment
Mocked properties can be reassigned:
const service = createMock<{ value: number }>();
service.value = 42;
expect(service.value).toBe(42);
service.value = 99;
expect(service.value).toBe(99);Chaining Mock Return Values
interface Validator {
validate: () => boolean;
}
const validator = createMock<Validator>();
validator.validate
.mockReturnValueOnce(true)
.mockReturnValueOnce(false)
.mockReturnValueOnce(true);
expect(validator.validate()).toBe(true);
expect(validator.validate()).toBe(false);
expect(validator.validate()).toBe(true);Mocking Async Functions
interface AsyncService {
fetchUser: (id: number) => Promise<{ id: number; name: string }>;
}
const service = createMock<AsyncService>({
fetchUser: async (id) => ({ id, name: 'User ' + id }),
});
const user = await service.fetchUser(123);
expect(user).toEqual({ id: 123, name: 'User 123' });
expect(service.fetchUser).toHaveBeenCalledWith(123);Comparison with Other Solutions
vs Manual Mocking
Manual:
const service = {
method1: vi.fn(),
method2: vi.fn(),
nested: {
method3: vi.fn(),
},
} as unknown as MyService;With vitest-create-mock:
const service = createMock<MyService>();The API is intentionally identical to make migration seamless.
How It Works
vitest-create-mock uses JavaScript Proxies to:
- Intercept property access on the mocked object
- Return Vitest mocks (
vi.fn()) for methods - Recursively create nested proxies for deep mocking
- Cache created mocks for consistent behavior
- Allow property assignment and mock configuration
This approach provides a powerful, flexible mocking solution with minimal boilerplate.
TypeScript Support
This library is written in TypeScript and provides full type safety:
- Type inference for mocked methods
- Autocomplete for all properties and methods
- Type checking for partial implementations
- Proper typing for nested objects and return values
Development
Prerequisites
- Node.js
- pnpm
Commands
# Install dependencies
pnpm install
# Run tests
pnpm test
# Run tests in watch mode
pnpm run dev
# Build the library
pnpm run build
# Type check
pnpm run typecheckContributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT © Jake Klassen
Credits
This library is a port of the excellent @golevelup/ts-jest library for Vitest. Thanks to the original authors for their work!
Related Projects
- Vitest - Next generation testing framework
- @golevelup/ts-jest - The original Jest version
