0
0
NestJSframework~15 mins

Unit testing controllers in NestJS - Deep Dive

Choose your learning style9 modes available
Overview - Unit testing controllers
What is it?
Unit testing controllers means checking if the controller parts of a NestJS app work correctly by themselves. Controllers handle incoming requests and send responses. Unit tests focus on these controllers without involving other parts like databases or services. This helps catch mistakes early and ensures each controller behaves as expected.
Why it matters
Without unit testing controllers, bugs can hide in how requests are handled or responses are sent, causing errors in the app. It’s like not checking if a cashier scans items correctly before opening the store. Unit tests save time and effort by catching problems early, making the app more reliable and easier to fix.
Where it fits
Before learning unit testing controllers, you should understand basic NestJS controllers and services. After mastering unit testing controllers, you can learn integration testing to check how controllers work with other parts together, and end-to-end testing for the whole app.
Mental Model
Core Idea
Unit testing controllers means isolating the controller to check its behavior without involving real services or databases.
Think of it like...
Testing a controller is like checking if a receptionist correctly directs visitors without involving the whole office staff or building.
┌───────────────┐
│ Controller    │
│ (handles req) │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Mocked Service│
│ (fake helpers)│
└───────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding NestJS Controllers
🤔
Concept: Learn what controllers do in NestJS and their role in handling requests.
Controllers in NestJS are classes decorated with @Controller. They define methods to respond to HTTP requests like GET or POST. Each method corresponds to a route and returns data or responses.
Result
You can identify controller methods and their routes in a NestJS app.
Knowing what controllers do is essential before testing them, as tests focus on their input and output behavior.
2
FoundationBasics of Unit Testing in NestJS
🤔
Concept: Learn how to write simple unit tests using Jest in NestJS.
NestJS uses Jest by default for testing. A unit test checks a small piece of code in isolation. You write test cases using describe and it blocks, and use expect to check results.
Result
You can write and run a basic test that checks a function’s output.
Understanding Jest basics lets you write tests that verify controller behavior step-by-step.
3
IntermediateMocking Services in Controller Tests
🤔Before reading on: Do you think controller tests should use real services or fake ones? Commit to your answer.
Concept: Learn to replace real services with mocks to isolate controller tests.
Controllers often call services to get data. In unit tests, you replace these services with mocks—fake versions that return fixed data. This keeps tests focused on the controller, not the service logic.
Result
Controller tests run quickly and only fail if the controller code is wrong, not because of service issues.
Mocking services prevents unrelated failures and speeds up tests, making debugging easier.
4
IntermediateTesting Controller Methods with Jest
🤔Before reading on: Should controller tests check HTTP status codes or just returned data? Commit to your answer.
Concept: Learn to test controller methods for correct responses and status codes.
Use Jest to call controller methods directly in tests. Check if they return expected data and status codes. For example, test if a GET method returns the right list or if a POST method returns success.
Result
You verify that controller methods behave as expected for different inputs.
Testing both data and status codes ensures the controller handles requests fully and correctly.
5
IntermediateUsing NestJS TestingModule for Controllers
🤔
Concept: Learn to create a TestingModule to instantiate controllers with mocked dependencies.
NestJS provides TestingModule to build a test environment. You declare the controller and provide mocked services. This simulates the real app but with controlled dependencies.
Result
You get a controller instance ready for testing with all dependencies mocked.
Using TestingModule follows NestJS patterns and ensures tests are realistic yet isolated.
6
AdvancedHandling Async Methods in Controller Tests
🤔Before reading on: Do you think async controller methods need special test handling? Commit to your answer.
Concept: Learn to test controller methods that return promises or use async/await.
Many controller methods are async because they call services that fetch data. In tests, use async/await or return promises to wait for results before asserting. This avoids false positives or negatives.
Result
Tests correctly handle asynchronous behavior and verify results after promises resolve.
Proper async handling prevents flaky tests and ensures accurate verification of controller logic.
7
ExpertTesting Controllers with Exception Filters
🤔Before reading on: Do you think controller tests should check error handling behavior? Commit to your answer.
Concept: Learn to test how controllers handle errors and exceptions using NestJS exception filters.
Controllers may throw exceptions or errors. NestJS uses exception filters to catch and format these. In tests, simulate errors from services and check if controllers respond with correct error messages and status codes.
Result
You verify that controllers handle failures gracefully and return proper error responses.
Testing error paths ensures your app is robust and user-friendly even when things go wrong.
Under the Hood
NestJS controllers are classes with methods mapped to routes. When a request arrives, NestJS calls the matching method. Unit tests create controller instances with mocked dependencies to isolate logic. Jest runs tests by executing these methods and checking outputs synchronously or asynchronously.
Why designed this way?
NestJS separates controllers from services to keep code organized and testable. Mocking dependencies in tests follows the principle of isolation, making tests reliable and fast. This design avoids side effects from databases or external APIs during unit tests.
┌───────────────┐
│ HTTP Request  │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Controller    │
│ (method call) │
└──────┬────────┘
       │ calls
       ▼
┌───────────────┐
│ Service       │
│ (mocked in    │
│  tests)       │
└───────────────┘
       │
       ▼
┌───────────────┐
│ Response      │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Should unit tests for controllers include real database calls? Commit yes or no.
Common Belief:Unit tests should use the real database to test controllers fully.
Tap to reveal reality
Reality:Unit tests should never use real databases; they must mock all external dependencies to isolate the controller.
Why it matters:Using real databases makes tests slow, flaky, and harder to debug, defeating the purpose of unit testing.
Quick: Do you think testing only the returned data from controllers is enough? Commit yes or no.
Common Belief:Checking returned data alone is enough to test controllers.
Tap to reveal reality
Reality:Controllers also set HTTP status codes and headers, which must be tested for full correctness.
Why it matters:Ignoring status codes can cause clients to misinterpret responses, leading to bugs in the app.
Quick: Do you think mocking services in controller tests hides real bugs? Commit yes or no.
Common Belief:Mocking services hides real bugs and makes tests less useful.
Tap to reveal reality
Reality:Mocking isolates controller logic, so tests focus on controller behavior, not service bugs.
Why it matters:Without mocking, tests become integration tests and lose focus, making debugging harder.
Quick: Is it okay to skip testing error handling in controllers? Commit yes or no.
Common Belief:Error handling in controllers is not important to test in unit tests.
Tap to reveal reality
Reality:Testing error handling ensures the app responds correctly to failures and improves reliability.
Why it matters:Skipping error tests can let bugs slip through, causing crashes or bad user experiences.
Expert Zone
1
Mock implementations can be reused across multiple tests to reduce duplication and improve maintainability.
2
Testing private controller methods is usually unnecessary; focus on public route handlers for meaningful tests.
3
Controllers should remain thin; complex logic belongs in services, which simplifies controller tests and improves design.
When NOT to use
Unit testing controllers is not suitable for verifying integration with real services or databases. For those, use integration or end-to-end tests that cover multiple layers together.
Production Patterns
In real projects, controllers are tested with mocked services using NestJS TestingModule. Tests cover normal responses, edge cases, and error handling. Continuous integration runs these tests automatically to catch regressions early.
Connections
Mocking in Software Testing
Builds-on
Understanding mocking in controller tests helps grasp how to isolate parts of any software for focused testing.
Single Responsibility Principle
Builds-on
Knowing that controllers should only handle request routing clarifies why their tests focus on input-output, not business logic.
Customer Service Reception
Analogy
Seeing controllers as receptionists helps understand their role in directing requests and why testing their responses matters.
Common Pitfalls
#1Testing controllers with real services causes slow and flaky tests.
Wrong approach:const app = await Test.createTestingModule({ controllers: [MyController], providers: [MyService], // real service }).compile();
Correct approach:const mockService = { method: jest.fn().mockReturnValue('data') }; const app = await Test.createTestingModule({ controllers: [MyController], providers: [{ provide: MyService, useValue: mockService }], }).compile();
Root cause:Not mocking dependencies leads to tests depending on external code and state.
#2Ignoring async behavior causes tests to pass or fail incorrectly.
Wrong approach:it('returns data', () => { const result = controller.getData(); expect(result).toBe('data'); });
Correct approach:it('returns data', async () => { const result = await controller.getData(); expect(result).toBe('data'); });
Root cause:Forgetting to await async methods means tests check promises, not resolved values.
#3Not testing error handling lets bugs slip through.
Wrong approach:it('returns data', async () => { jest.spyOn(service, 'getData').mockImplementation(() => { throw new Error('fail'); }); const result = await controller.getData(); expect(result).toBeDefined(); });
Correct approach:it('throws error properly', async () => { jest.spyOn(service, 'getData').mockImplementation(() => { throw new Error('fail'); }); await expect(controller.getData()).rejects.toThrow('fail'); });
Root cause:Not asserting error cases means tests miss verifying controller robustness.
Key Takeaways
Unit testing controllers means isolating them from real services by using mocks to focus on their behavior.
Controllers handle requests and responses, so tests must check returned data and HTTP status codes.
NestJS TestingModule helps create test environments with mocked dependencies following framework patterns.
Properly handling async methods and error cases in tests ensures reliable and meaningful results.
Avoid using real databases or services in unit tests to keep tests fast, stable, and easy to debug.