Bird
Raised Fist0
NestJSframework~15 mins

Unit testing controllers in NestJS - Deep Dive

Choose your learning style10 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.

Practice

(1/5)
1. What is the main purpose of unit testing a controller in NestJS?
easy
A. To verify the controller handles requests correctly without depending on other parts
B. To test the database connection and queries
C. To check the UI rendering of the application
D. To measure the performance of the entire application

Solution

  1. Step 1: Understand the role of a controller

    Controllers handle incoming requests and return responses, so testing them focuses on this behavior.
  2. Step 2: Identify what unit testing isolates

    Unit testing isolates the controller from other parts like services or databases to test its logic alone.
  3. Final Answer:

    To verify the controller handles requests correctly without depending on other parts -> Option A
  4. Quick Check:

    Unit testing controller logic = To verify the controller handles requests correctly without depending on other parts [OK]
Hint: Controllers handle requests; test them isolated from services [OK]
Common Mistakes:
  • Confusing unit tests with integration tests
  • Testing database or UI in controller unit tests
  • Ignoring mocking dependencies
2. Which of the following is the correct way to create a testing module for a controller in NestJS?
easy
A. const module = Test.createModule({ providers: [MyController] });
B. const module = new Module({ controllers: [MyController] });
C. const module = await Test.createTestingModule({ controllers: [MyController] }).compile();
D. const module = await Test.createTestingModule({ services: [MyController] }).compile();

Solution

  1. Step 1: Recall NestJS testing syntax

    Use Test.createTestingModule with a config object specifying controllers or providers, then compile it.
  2. Step 2: Identify correct property for controllers

    The property to register controllers is 'controllers', not 'services' or 'providers' for controllers.
  3. Final Answer:

    const module = await Test.createTestingModule({ controllers: [MyController] }).compile(); -> Option C
  4. Quick Check:

    Use controllers array in createTestingModule = const module = await Test.createTestingModule({ controllers: [MyController] }).compile(); [OK]
Hint: Use controllers: [] inside Test.createTestingModule [OK]
Common Mistakes:
  • Using 'services' instead of 'controllers' key
  • Forgetting to await compile()
  • Using new Module() instead of Test.createTestingModule
3. Given this controller method and test snippet, what will result be?
class MyController {
  getHello() {
    return 'Hello World!';
  }
}

// Test snippet
const controller = new MyController();
const result = controller.getHello();
medium
A. null
B. undefined
C. An error because getHello is async
D. 'Hello World!'

Solution

  1. Step 1: Analyze the controller method

    The method getHello returns the string 'Hello World!' synchronously.
  2. Step 2: Check the test call

    Calling getHello() directly returns the string without errors or async behavior.
  3. Final Answer:

    'Hello World!' -> Option D
  4. Quick Check:

    Direct method call returns string = 'Hello World!' [OK]
Hint: Direct call returns method's return value immediately [OK]
Common Mistakes:
  • Assuming async behavior without async keyword
  • Expecting undefined if not mocked
  • Confusing method call with property access
4. What is wrong with this unit test for a NestJS controller?
describe('MyController', () => {
  let controller: MyController;

  beforeEach(() => {
    controller = new MyController();
  });

  it('should return greeting', () => {
    expect(controller.getHello()).toBe('Hello World!');
  });
});
medium
A. The controller is not created using Test.createTestingModule
B. The test does not mock the service dependency
C. The test is missing async keyword
D. The expect statement uses toBe instead of toEqual

Solution

  1. Step 1: Check controller instantiation

    Creating controller directly is allowed but only if it has no dependencies.
  2. Step 2: Identify missing mocking

    If the controller depends on a service, it must be mocked to isolate controller logic.
  3. Final Answer:

    The test does not mock the service dependency -> Option B
  4. Quick Check:

    Missing mocks for dependencies = The test does not mock the service dependency [OK]
Hint: Always mock services when testing controllers with dependencies [OK]
Common Mistakes:
  • Assuming direct instantiation is always wrong
  • Forgetting to mock injected services
  • Confusing toBe and toEqual for strings
5. You want to unit test a NestJS controller method that calls a service method returning a promise. Which approach correctly mocks the service to test the controller method synchronously?
hard
A. Mock the service method with jest.fn().mockResolvedValue(value) and await the controller method in the test
B. Mock the service method with jest.fn().mockReturnValue(value) without async/await
C. Call the real service method without mocking to test actual behavior
D. Mock the service method with jest.fn().mockImplementation(() => { throw new Error(); })

Solution

  1. Step 1: Understand async service method

    The service method returns a promise, so the mock must simulate a resolved promise.
  2. Step 2: Use jest.fn().mockResolvedValue

    This creates a mock that returns a resolved promise with the given value, allowing the controller method to await it.
  3. Step 3: Await controller method in test

    The test must await the controller method to handle the async call properly.
  4. Final Answer:

    Mock the service method with jest.fn().mockResolvedValue(value) and await the controller method in the test -> Option A
  5. Quick Check:

    Use mockResolvedValue and await async calls = Mock the service method with jest.fn().mockResolvedValue(value) and await the controller method in the test [OK]
Hint: Use mockResolvedValue for async mocks and await calls [OK]
Common Mistakes:
  • Using mockReturnValue for async methods
  • Not awaiting async controller methods
  • Testing real service instead of mocking
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard