Comprehensive testing utilities for NestJS applications with dependency injection testing capabilities
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
System for replacing providers, guards, pipes, filters, and interceptors with test doubles. This enables precise control over dependencies during testing, allowing developers to isolate components and create predictable test scenarios.
The TestingModuleBuilder provides specific methods for overriding different types of NestJS components.
class TestingModuleBuilder {
/**
* Override a provider with a test double
* @param typeOrToken - The provider class or injection token to override
* @returns OverrideBy interface for specifying the replacement
*/
overrideProvider<T = any>(typeOrToken: T): OverrideBy;
/**
* Override a pipe with a test double
* @param typeOrToken - The pipe class or injection token to override
* @returns OverrideBy interface for specifying the replacement
*/
overridePipe<T = any>(typeOrToken: T): OverrideBy;
/**
* Override a guard with a test double
* @param typeOrToken - The guard class or injection token to override
* @returns OverrideBy interface for specifying the replacement
*/
overrideGuard<T = any>(typeOrToken: T): OverrideBy;
/**
* Override a filter with a test double
* @param typeOrToken - The filter class or injection token to override
* @returns OverrideBy interface for specifying the replacement
*/
overrideFilter<T = any>(typeOrToken: T): OverrideBy;
/**
* Override an interceptor with a test double
* @param typeOrToken - The interceptor class or injection token to override
* @returns OverrideBy interface for specifying the replacement
*/
overrideInterceptor<T = any>(typeOrToken: T): OverrideBy;
}Interface providing methods to specify how a component should be overridden.
/**
* Interface for specifying component override strategies
*/
interface OverrideBy {
/**
* Replace with a specific value or instance
* @param value - The value to use as replacement
* @returns TestingModuleBuilder for method chaining
*/
useValue(value: any): TestingModuleBuilder;
/**
* Replace with a factory function result
* @param options - Factory configuration including function and dependencies
* @returns TestingModuleBuilder for method chaining
*/
useFactory(options: OverrideByFactoryOptions): TestingModuleBuilder;
/**
* Replace with an instance of a different class
* @param metatype - The class to instantiate as replacement
* @returns TestingModuleBuilder for method chaining
*/
useClass(metatype: any): TestingModuleBuilder;
}
/**
* Configuration options for factory-based overrides
*/
interface OverrideByFactoryOptions {
/**
* Factory function that creates the replacement instance
*/
factory: (...args: any[]) => any;
/**
* Optional array of dependencies to inject into the factory
*/
inject?: any[];
}Usage Examples:
import { Test, TestingModule } from "@nestjs/testing";
import { UsersService } from "./users.service";
import { DatabaseService } from "./database.service";
import { AuthGuard } from "./auth.guard";
import { LoggingInterceptor } from "./logging.interceptor";
describe("Provider Overriding", () => {
let module: TestingModule;
beforeEach(async () => {
// Override with useValue
const mockDatabaseService = {
findUser: jest.fn().mockResolvedValue({ id: 1, name: "Test User" }),
saveUser: jest.fn().mockResolvedValue({ id: 1 }),
};
module = await Test.createTestingModule({
providers: [UsersService, DatabaseService],
controllers: [UsersController],
})
.overrideProvider(DatabaseService)
.useValue(mockDatabaseService)
.compile();
});
it("should use mocked database service", async () => {
const usersService = module.get<UsersService>(UsersService);
const user = await usersService.findUser(1);
expect(user.name).toBe("Test User");
});
});
// Override with useClass
class MockAuthGuard {
canActivate() {
return true;
}
}
const moduleWithClassOverride = await Test.createTestingModule({
providers: [UsersService],
controllers: [UsersController],
})
.overrideGuard(AuthGuard)
.useClass(MockAuthGuard)
.compile();
// Override with useFactory
const moduleWithFactory = await Test.createTestingModule({
providers: [UsersService, DatabaseService, ConfigService],
})
.overrideProvider(DatabaseService)
.useFactory({
factory: (config: ConfigService) => ({
findUser: jest.fn(),
connection: config.getDatabaseUrl(),
}),
inject: [ConfigService],
})
.compile();
// Override interceptor
const moduleWithInterceptor = await Test.createTestingModule({
providers: [UsersService],
controllers: [UsersController],
})
.overrideInterceptor(LoggingInterceptor)
.useValue({
intercept: jest.fn((context, next) => next.handle()),
})
.compile();Partial Mocking:
// Override with partial implementation
const partialMockService = {
findUser: jest.fn().mockResolvedValue({ id: 1, name: "Test" }),
// Other methods will use original implementation or throw
};
const module = await Test.createTestingModule({
providers: [UsersService, DatabaseService],
})
.overrideProvider(DatabaseService)
.useValue(partialMockService)
.compile();Spy Integration:
// Create spy-enabled mock
const databaseServiceSpy = {
findUser: jest.fn(),
saveUser: jest.fn(),
deleteUser: jest.fn(),
};
const module = await Test.createTestingModule({
providers: [UsersService, DatabaseService],
})
.overrideProvider(DatabaseService)
.useValue(databaseServiceSpy)
.compile();
// Verify interactions
expect(databaseServiceSpy.findUser).toHaveBeenCalledWith(1);Token-based Overrides:
const DATABASE_TOKEN = Symbol("DATABASE_TOKEN");
const module = await Test.createTestingModule({
providers: [
UsersService,
{
provide: DATABASE_TOKEN,
useClass: DatabaseService,
},
],
})
.overrideProvider(DATABASE_TOKEN)
.useValue(mockDatabase)
.compile();Common override scenarios and error handling:
// Handle missing dependencies
try {
const module = await Test.createTestingModule({
providers: [ServiceWithDependencies],
})
.overrideProvider(MissingDependency)
.useValue(mockDependency)
.compile();
} catch (error) {
// Handle compilation errors
}
// Override non-existent provider (will be ignored)
const module = await Test.createTestingModule({
providers: [UsersService],
})
.overrideProvider(NonExistentService) // This won't cause an error
.useValue(mockService)
.compile();import { TestingModuleBuilder } from "./testing-module.builder";
interface OverrideBy {
useValue: (value: any) => TestingModuleBuilder;
useFactory: (options: OverrideByFactoryOptions) => TestingModuleBuilder;
useClass: (metatype: any) => TestingModuleBuilder;
}
interface OverrideByFactoryOptions {
factory: (...args: any[]) => any;
inject?: any[];
}