NestJS Unit Testing Tutorial for Services and Controllers
Testing a plain function, as in the previous lesson, is simple: call it, check the result. Testing a NestJS service or controller is slightly different, since these classes typically depend on other injected providers. This lesson covers NestJS's official testing utilities, specifically designed to build an isolated, minimal dependency injection context for exactly this purpose.
NestJS Unit Testing Services Controllers: Learning Objectives
- Understand why NestJS provides a dedicated Test module for building test-specific DI contexts.
- Use Test.createTestingModule() to construct a testing module for a service.
- Retrieve an instance of the class under test using module.get().
- Write focused unit tests for a service's individual methods.
- Apply the same TestingModule pattern to unit test a controller.
NestJS Unit Testing Services Controllers: Key Terms and Definitions
- TestingModule: A specialized NestJS module, built via Test.createTestingModule(), used to construct an isolated dependency injection context specifically for testing.
- Test.createTestingModule(): The NestJS testing utility function that begins building a TestingModule, accepting the same providers/controllers/imports shape as a regular @Module().
- module.get(): A method on a compiled TestingModule used to retrieve an instance of a specific provider or controller for use in test assertions.
- beforeEach(): A Jest function that runs a setup block before every individual test in a describe block, commonly used to rebuild a fresh TestingModule for each test.
- Unit under test: The specific class or function a given test file is focused on verifying, as distinct from its dependencies.
How NestJS Unit Testing Services Controllers Works: Detailed Explanation
A NestJS service, like UsersService from Module 3, typically depends on injected providers, a TypeORM repository, for instance. Testing it in true isolation means we don't want to accidentally spin up a real database connection just to test business logic; we want to verify UsersService's own logic specifically, trusting that its dependencies behave as expected (and testing those dependencies separately, in their own test files).
NestJS's official @nestjs/testing package provides exactly the tool for this: Test.createTestingModule(), which accepts the same shape of configuration object as a regular @Module() decorator, controllers, providers, imports, letting you construct a testing module including only the specific service (or controller) you're testing, along with either its real dependencies or, more commonly, mock replacements standing in for them (mocking is covered in depth in the next lesson).
After configuring the testing module's metadata, calling .compile() on it (an asynchronous operation) actually builds the module and resolves its dependency injection graph, exactly like NestFactory.create() does for a real application, just scoped narrowly to only what you've included. Once compiled, module.get(ServiceName) retrieves a fully-instantiated instance of that service, with its dependencies correctly injected, ready to call its methods directly in your test assertions.
A common, idiomatic pattern wraps this setup inside a beforeEach() block, which Jest runs fresh before every single test case in a describe block. This ensures each test starts with a brand-new, freshly compiled testing module and service instance, preventing any state from one test accidentally leaking into and affecting another, a subtle but important source of flaky, unreliable tests if overlooked.
Testing a controller follows the exact same TestingModule pattern, just including the controller in the configuration instead of (or alongside) a service, letting you call the controller's methods directly and assert on their return values, without any of NestJS's actual HTTP routing, guards, or pipes layer involved at all, since a true unit test isolates the controller's own logic specifically, deliberately leaving broader integration concerns (covered in a later lesson) for a different kind of test.
Interview-Friendly Explanation
A strong interview or viva answer for nestjs unit testing services controllers must go beyond a one-line definition. Interviewers evaluating backend developers reward answers that combine a precise definition, a clear working explanation, a real-world example, and a conclusion that highlights why it matters for shipping reliable production Node.js applications. Use the four-point framework below when answering under time pressure.
- Definition point: TestingModule: A specialized NestJS module, built via Test.createTestingModule(), used to construct an isolated dependency injection context specifically for testing.
- Working point: Use Test.createTestingModule() to construct a testing module for a service.
- Example point: Virtually every service and controller in a well-tested NestJS codebase has a corresponding .spec.ts file following this exact Test.createTestingModule() and module.get() pattern.
- Conclusion point: Test.createTestingModule() is NestJS's answer to properly testing classes that depend on dependency injection, without manually wiring dependencies by hand.
How to Answer This in a Technical Interview
- Give a two-to-three sentence definition using correct testing terminology (unit, integration, e2e).
- Add one specific example drawn from a real backend scenario such as testing an auth flow or a CRUD API.
- Mention a relevant trade-off, such as speed versus realism, since interviewers often probe this contrast.
- Close with one benefit, limitation, or production consequence of getting this wrong.
- Avoid vague answers like "it just makes sure the code works" — interviewers filter these out immediately.
Practical Scenario
Imagine you are shipping frequent changes to a production backend for a SaaS product, similar to systems used at companies like Razorpay, Freshworks, or Zomato. Every lesson in this module maps directly to a decision you will make to ship confidently: what to test, how to isolate it, and how to know your test suite is actually catching real problems before they reach users.
NestJS Unit Testing Services Controllers: Architecture and Flow Diagram
Visualize the TestingModule setup flow:
[Test.createTestingModule({ providers: [UsersService, ...] })] --> [.compile()] --> [TestingModule instance] --> [module.get(UsersService)] --> [Fully-instantiated UsersService, ready for test assertions]
Typically wrapped in beforeEach() so a fresh module/instance is built before every individual test case.
NestJS Unit Testing Services Controllers: Step Reference Table
| Step | Method/Function | Purpose |
|---|---|---|
| 1 | Test.createTestingModule({...}) | Defines the testing module's providers, controllers, and imports |
| 2 | .compile() | Builds the module and resolves its dependency injection graph |
| 3 | module.get(ClassName) | Retrieves a fully-instantiated instance of a specific provider or controller |
| 4 | beforeEach() | Rebuilds a fresh module/instance before each individual test, avoiding shared state |
NestJS Unit Testing Services Controllers: NestJS Code Example
// users.service.ts (simplified, from earlier modules)
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
private users = [{ id: 1, name: 'Asha Mehta', isActive: true }];
findAll() {
return this.users;
}
findActiveCount(): number {
return this.users.filter((u) => u.isActive).length;
}
}
// users.service.spec.ts — unit testing the service in isolation
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should return all users', () => {
const result = service.findAll();
expect(result).toHaveLength(1);
expect(result[0].name).toBe('Asha Mehta');
});
it('should count only active users', () => {
expect(service.findActiveCount()).toBe(1);
});
});
// users.controller.spec.ts — unit testing the controller the same way
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
describe('UsersController', () => {
let controller: UsersController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [UsersService],
}).compile();
controller = module.get<UsersController>(UsersController);
});
it('should return all users from the service', () => {
expect(controller.findAll()).toHaveLength(1);
});
});
The users.service.spec.ts file's beforeEach() block runs before every test, calling Test.createTestingModule({ providers: [UsersService] }).compile() to build a fresh, isolated module containing only UsersService, then retrieving it with module.get(UsersService). Since UsersService has no other dependencies in this simplified example, this is the entire setup needed; each test then calls a method on the real service instance and asserts on its return value directly. users.controller.spec.ts follows the identical pattern, but includes UsersController in the controllers array and its real UsersService dependency in providers, since the controller needs UsersService injected into its constructor exactly as it would in a real running application, letting the test call controller.findAll() directly and verify it correctly delegates to and returns the service's data.
Real-World NestJS Unit Testing Services Controllers Industry Examples
- Virtually every service and controller in a well-tested NestJS codebase has a corresponding .spec.ts file following this exact Test.createTestingModule() and module.get() pattern.
- Teams practicing test-driven development often write the beforeEach() setup and a 'should be defined' sanity-check test as the very first step when starting a new service, before writing any of its actual business logic.
- Code review tooling at many companies automatically flags new service or controller files that don't have a corresponding spec file, treating this pattern as a near-mandatory convention.
- Refactoring a service's internal implementation with confidence, without breaking its public behavior, is one of the most valuable practical benefits this kind of isolated unit test provides in real, evolving codebases.
NestJS Unit Testing Services Controllers Interview Questions and Answers
Q1. Why does NestJS provide Test.createTestingModule() instead of just instantiating a service directly with 'new'?
Short answer: Test.createTestingModule() builds a proper, isolated dependency injection context, allowing a service's real dependencies to be correctly injected (or replaced with mocks, covered in the next lesson) exactly as NestJS's actual DI system would in a running application, which manually calling 'new UsersService()' could not replicate correctly for services with their own injected dependencies.
Detailed explanation: A NestJS service, like UsersService from Module 3, typically depends on injected providers, a TypeORM repository, for instance. Testing it in true isolation means we don't want to accidentally spin up a real database connection just to test business logic; we want to verify UsersService's own logic specifically, trusting that its dependencies behave as expected (and testing those dependencies separately, in their own test files). NestJS's official @nestjs/testing package provides exactly the tool for this: Test.createTestingModule(), which accepts the same shape of configuration object as a regular @Module() decorator, controllers, providers, imports, letting you construct a testing module including only the specific service (or controller) you're testing, along with either its real dependencies or, more commonly, mock replacements standing in for them (mocking is covered in depth in the next lesson). After configuring the testing module's metadata, calling .compile() on it (an asynchronous operation) actually builds the module and resolves its dependency injection graph, exactly like NestFactory.create() does for a real application, just scoped narrowly to only what you've included. Once compiled, module.get(ServiceName) retrieves a fully-instantiated instance of that service, with its dependencies correctly injected, ready to call its methods directly in your test assertions. A common, idiomatic pattern wraps this setup inside a beforeEach() block, which Jest runs fresh before every single test case in a describe block. This ensures each test starts with a brand-new, freshly compiled testing module and service instance, preventing any state from one test accidentally leaking into and affecting another, a subtle but important source of flaky, unreliable tests if overlooked. Testing a controller follows the exact same TestingModule pattern, just including the controller in the configuration instead of (or alongside) a service, letting you call the controller's methods directly and assert on their return values, without any of NestJS's actual HTTP routing, guards, or pipes layer involved at all, since a true unit test isolates the controller's own logic specifically, deliberately leaving broader integration concerns (covered in a later lesson) for a different kind of test.
Practical example: Virtually every service and controller in a well-tested NestJS codebase has a corresponding .spec.ts file following this exact Test.createTestingModule() and module.get() pattern.
Interview tip: Test.createTestingModule({ providers/controllers/imports }) mirrors @Module()'s shape, scoped specifically for testing.
Revision hook: Test.createTestingModule() is NestJS's answer to properly testing classes that depend on dependency injection, without manually wiring dependencies by hand.
Q2. Walk through the steps of writing a basic unit test for a NestJS service.
Short answer: You call Test.createTestingModule() with a providers array including the service under test (and any dependencies it needs), call .compile() to build the module asynchronously, retrieve an instance of the service using module.get(ServiceName), and then write individual it() test cases calling the service's methods and asserting on their return values using expect().
Detailed explanation: The users.service.spec.ts file's beforeEach() block runs before every test, calling Test.createTestingModule({ providers: [UsersService] }).compile() to build a fresh, isolated module containing only UsersService, then retrieving it with module.get(UsersService). Since UsersService has no other dependencies in this simplified example, this is the entire setup needed; each test then calls a method on the real service instance and asserts on its return value directly. users.controller.spec.ts follows the identical pattern, but includes UsersController in the controllers array and its real UsersService dependency in providers, since the controller needs UsersService injected into its constructor exactly as it would in a real running application, letting the test call controller.findAll() directly and verify it correctly delegates to and returns the service's data.
Practical example: Teams practicing test-driven development often write the beforeEach() setup and a 'should be defined' sanity-check test as the very first step when starting a new service, before writing any of its actual business logic.
Interview tip: .compile() builds the testing module and resolves its DI graph; module.get(ClassName) retrieves an instance.
Revision hook: The compile-then-get pattern is the foundational workflow you'll repeat in nearly every unit test file you write in NestJS.
Q3. Why is beforeEach() commonly used to rebuild the testing module before every test?
Short answer: Rebuilding a fresh testing module and service instance before each individual test prevents any state changes made during one test from accidentally leaking into and affecting a subsequent test, which is important for keeping tests independent, reliable, and free of order-dependent failures.
Detailed explanation: NestJS's official @nestjs/testing package provides Test.createTestingModule(), a utility that builds an isolated dependency injection context specifically for unit testing, accepting the same providers/controllers/imports configuration shape as a regular @Module(). After calling .compile() to asynchronously build this testing module, module.get(ClassName) retrieves a fully-instantiated instance of the service or controller under test, with its dependencies correctly resolved, ready to call directly in test assertions. Wrapping this setup in a beforeEach() block, so a fresh module and instance are built before every individual test case, keeps tests properly isolated and prevents state from leaking between them. This exact same pattern, createTestingModule, compile, get, applies identically whether you're unit testing a service or a controller, forming the foundational workflow for nearly all NestJS unit testing.
Practical example: Code review tooling at many companies automatically flags new service or controller files that don't have a corresponding spec file, treating this pattern as a near-mandatory convention.
Interview tip: beforeEach() rebuilding the module before each test keeps tests independent and avoids shared-state bugs.
Revision hook: Rebuilding fresh state before every test via beforeEach() is a small habit that prevents an entire category of flaky test bugs.
Q4. Can the same TestingModule pattern be used to unit test a controller, not just a service?
Short answer: Yes, the identical pattern applies: you include the controller in the controllers array (and its dependencies, like a service, in providers) when calling Test.createTestingModule(), then retrieve the controller instance with module.get() and call its methods directly to test its logic in isolation from NestJS's actual HTTP layer.
Detailed explanation: A NestJS service, like UsersService from Module 3, typically depends on injected providers, a TypeORM repository, for instance. Testing it in true isolation means we don't want to accidentally spin up a real database connection just to test business logic; we want to verify UsersService's own logic specifically, trusting that its dependencies behave as expected (and testing those dependencies separately, in their own test files). NestJS's official @nestjs/testing package provides exactly the tool for this: Test.createTestingModule(), which accepts the same shape of configuration object as a regular @Module() decorator, controllers, providers, imports, letting you construct a testing module including only the specific service (or controller) you're testing, along with either its real dependencies or, more commonly, mock replacements standing in for them (mocking is covered in depth in the next lesson). After configuring the testing module's metadata, calling .compile() on it (an asynchronous operation) actually builds the module and resolves its dependency injection graph, exactly like NestFactory.create() does for a real application, just scoped narrowly to only what you've included. Once compiled, module.get(ServiceName) retrieves a fully-instantiated instance of that service, with its dependencies correctly injected, ready to call its methods directly in your test assertions. A common, idiomatic pattern wraps this setup inside a beforeEach() block, which Jest runs fresh before every single test case in a describe block. This ensures each test starts with a brand-new, freshly compiled testing module and service instance, preventing any state from one test accidentally leaking into and affecting another, a subtle but important source of flaky, unreliable tests if overlooked. Testing a controller follows the exact same TestingModule pattern, just including the controller in the configuration instead of (or alongside) a service, letting you call the controller's methods directly and assert on their return values, without any of NestJS's actual HTTP routing, guards, or pipes layer involved at all, since a true unit test isolates the controller's own logic specifically, deliberately leaving broader integration concerns (covered in a later lesson) for a different kind of test.
Practical example: Refactoring a service's internal implementation with confidence, without breaking its public behavior, is one of the most valuable practical benefits this kind of isolated unit test provides in real, evolving codebases.
Interview tip: The same pattern (createTestingModule + compile + get) works identically for both services and controllers.
Revision hook: Unit tests for controllers should stay focused on the controller's own logic, leaving HTTP-layer concerns to integration and e2e tests.
NestJS Unit Testing Services Controllers MCQs and Practice Questions
1. Which NestJS utility function begins building an isolated testing module?
- NestFactory.create()
- Test.createTestingModule()
- ModuleRef.get()
- TestingModule.build()
Answer: B. Test.createTestingModule()
Explanation: Test.createTestingModule(), from the @nestjs/testing package, is the entry point for constructing an isolated dependency injection context specifically for unit testing.
Concept link: Test.createTestingModule({ providers/controllers/imports }) mirrors @Module()'s shape, scoped specifically for testing.
Why this matters: Test.createTestingModule() is NestJS's answer to properly testing classes that depend on dependency injection, without manually wiring dependencies by hand.
2. What method must be called on a TestingModule builder before it can be used?
- .build()
- .init()
- .compile()
- .resolve()
Answer: C. .compile()
Explanation: .compile() is the asynchronous method that actually builds the testing module and resolves its dependency injection graph, similar to how NestFactory.create() works for a real application.
Concept link: .compile() builds the testing module and resolves its DI graph; module.get(ClassName) retrieves an instance.
Why this matters: The compile-then-get pattern is the foundational workflow you'll repeat in nearly every unit test file you write in NestJS.
3. How do you retrieve an instance of a service from a compiled TestingModule?
- module.find()
- module.get(ServiceName)
- module.inject()
- new ServiceName()
Answer: B. module.get(ServiceName)
Explanation: module.get(ServiceName) retrieves a fully-instantiated instance of a specific provider or controller from the compiled testing module, ready for use in test assertions.
Concept link: beforeEach() rebuilding the module before each test keeps tests independent and avoids shared-state bugs.
Why this matters: Rebuilding fresh state before every test via beforeEach() is a small habit that prevents an entire category of flaky test bugs.
4. Why is beforeEach() commonly used in NestJS unit test files?
- To skip running certain tests
- To rebuild a fresh testing module and instance before every individual test
- To permanently share state across all tests
- To disable Jest's assertion checking
Answer: B. To rebuild a fresh testing module and instance before every individual test
Explanation: beforeEach() ensures each test starts from a clean, freshly compiled testing module and service instance, preventing state from one test from unintentionally affecting another.
Concept link: The same pattern (createTestingModule + compile + get) works identically for both services and controllers.
Why this matters: Unit tests for controllers should stay focused on the controller's own logic, leaving HTTP-layer concerns to integration and e2e tests.
Common NestJS Unit Testing Services Controllers Mistakes to Avoid
- Manually instantiating a service with 'new' instead of using Test.createTestingModule(), losing proper dependency injection for services with their own dependencies.
- Forgetting to include a service's own dependencies in the testing module's providers array, causing a 'cannot resolve dependency' error during .compile().
- Sharing a single testing module instance across all tests in a file without beforeEach(), risking state leaking between tests and causing unreliable, order-dependent failures.
- Testing a controller's HTTP-specific behavior (like status codes) in what should be a focused unit test, blurring the line between unit and integration/e2e testing concerns.
NestJS Unit Testing Services Controllers: Interview Notes and Exam Tips
- Test.createTestingModule({ providers/controllers/imports }) mirrors @Module()'s shape, scoped specifically for testing.
- .compile() builds the testing module and resolves its DI graph; module.get(ClassName) retrieves an instance.
- beforeEach() rebuilding the module before each test keeps tests independent and avoids shared-state bugs.
- The same pattern (createTestingModule + compile + get) works identically for both services and controllers.
Key NestJS Unit Testing Services Controllers Takeaways
- Test.createTestingModule() is NestJS's answer to properly testing classes that depend on dependency injection, without manually wiring dependencies by hand.
- The compile-then-get pattern is the foundational workflow you'll repeat in nearly every unit test file you write in NestJS.
- Rebuilding fresh state before every test via beforeEach() is a small habit that prevents an entire category of flaky test bugs.
- Unit tests for controllers should stay focused on the controller's own logic, leaving HTTP-layer concerns to integration and e2e tests.
NestJS Unit Testing Services Controllers: Summary
NestJS's official @nestjs/testing package provides Test.createTestingModule(), a utility that builds an isolated dependency injection context specifically for unit testing, accepting the same providers/controllers/imports configuration shape as a regular @Module(). After calling .compile() to asynchronously build this testing module, module.get(ClassName) retrieves a fully-instantiated instance of the service or controller under test, with its dependencies correctly resolved, ready to call directly in test assertions. Wrapping this setup in a beforeEach() block, so a fresh module and instance are built before every individual test case, keeps tests properly isolated and prevents state from leaking between them. This exact same pattern, createTestingModule, compile, get, applies identically whether you're unit testing a service or a controller, forming the foundational workflow for nearly all NestJS unit testing.