TestingModule
The TestingModule is a specialized, lightweight version of the NestJS Inversion of Control (IoC) container used exclusively for testing. It allows developers to selectively instantiate providers and controllers while overriding specific dependencies with mocks.
Overview
In a real application, the root AppModule recursively imports every module in your application, resolving thousands of dependencies and instantiating the HTTP server.
When writing a Unit Test for a specific service, you do not want to load the entire application. The Test.createTestingModule() method allows you to define a “miniature” module that contains only the specific class you want to test and the mocks required to satisfy its constructor.
Key Concepts
Test.createTestingModule({ ... }): The factory method used to define the imports, controllers, and providers for the test context. It accepts the exact same metadata object as the standard@Module()decorator..compile(): The asynchronous method that resolves all dependencies and bootstraps the testing container..get<T>(Token): The method used to retrieve an instantiated class (or mock) out of the compiled testing container.
Code Examples
1. Basic TestingModule Compilation
This is the standard boilerplate found in almost every *.spec.ts file generated by the NestJS CLI.
import { Test, TestingModule } from '@nestjs/testing';
import { AppService } from './app.service';
describe('AppService', () => {
let appService: AppService;
beforeEach(async () => {
// 1. Define the module
const moduleRef: TestingModule = await Test.createTestingModule({
providers: [AppService], // We only care about AppService
}).compile(); // 2. Compile the DI container
// 3. Extract the instantiated service
appService = moduleRef.get<AppService>(AppService);
});
it('should be defined', () => {
expect(appService).toBeDefined();
});
});
2. Overriding Providers
The true power of TestingModule lies in its ability to seamlessly swap real providers with mocks using the useValue syntax.
beforeEach(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
// Replace the real DatabaseService with a mock
{
provide: DatabaseService,
useValue: { query: jest.fn().mockResolvedValue([]) },
}
],
}).compile();
// Retrieve the mocked DatabaseService to run assertions on it later
const dbMock = moduleRef.get<DatabaseService>(DatabaseService);
});
3. Creating a Full Nest Application
When doing E2E testing, you use the TestingModule to bootstrap the full application (just like main.ts does), but instead of listening on a physical port, you return a headless INestApplication.
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule], // Import the root module
}).compile();
// Create the full app instance
const app = moduleFixture.createNestApplication();
// Apply global pipes/guards just like in main.ts
app.useGlobalPipes(new ValidationPipe());
// Initialize the app (starts lifecycle hooks like onModuleInit)
await app.init();
});
Best Practices
- Scope: Keep the
TestingModuleas small as possible. If you are unit testingUserService, do notimport: [UserModule]. Doing so will load theUserController,TypeOrmModule, and all other dependencies, making the test slow and defeating the purpose of an isolated unit test. Manually declare only theUserServicein theprovidersarray. resolve()vsget(): UsemoduleRef.get(Service)for standard singletons. If you are testing a Service that is heavily scoped (e.g.,Scope.REQUESTorScope.TRANSIENT), you must useawait moduleRef.resolve(Service)to instantiate it.