Bird
Raised Fist0
NestJSframework~15 mins

E2E testing with supertest in NestJS - Deep Dive

Choose your learning style10 modes available
Overview - E2E testing with supertest
What is it?
E2E testing with supertest means checking if your whole NestJS application works correctly from start to finish. It simulates real user actions by sending HTTP requests to your app and checking the responses. This helps ensure that all parts of your app work together as expected. Supertest is a tool that makes it easy to write these tests by handling HTTP requests and responses.
Why it matters
Without E2E testing, bugs can hide between parts of your app and cause unexpected problems for users. It’s like testing a car by only checking the engine but never driving it. E2E tests catch issues that unit tests miss because they test the app as a whole. This saves time and frustration by finding real problems before users do.
Where it fits
Before learning E2E testing with supertest, you should know basic NestJS app structure and how to write unit tests. After mastering E2E testing, you can explore advanced testing topics like mocking external services or continuous integration pipelines that run tests automatically.
Mental Model
Core Idea
E2E testing with supertest is like pretending to be a user who sends requests to your app and checks if the answers are right, testing the whole system together.
Think of it like...
Imagine ordering food at a restaurant: you place your order (send a request), the kitchen prepares it (your app processes), and the waiter brings your meal (response). E2E testing checks if this whole process works smoothly from order to delivery.
┌─────────────┐       HTTP Request       ┌─────────────┐
│   Test Code │ ───────────────────────▶ │ NestJS App  │
└─────────────┘                         └─────────────┘
       ▲                                         │
       │                                         ▼
       │                               HTTP Response (status, body)
       └─────────────────────────────────────────┘
Build-Up - 8 Steps
1
FoundationUnderstanding E2E Testing Basics
🤔
Concept: Learn what end-to-end testing means and why it tests the whole app flow.
E2E testing checks if your app works from the user's point of view. It sends real HTTP requests to your app and checks the responses. Unlike unit tests that test small parts, E2E tests check the entire system working together.
Result
You understand that E2E tests simulate real user interactions and cover the full app behavior.
Knowing that E2E tests cover the full app helps you see why they catch bugs unit tests miss.
2
FoundationSetting Up NestJS for Testing
🤔
Concept: Prepare a NestJS app to be tested by creating a test module and initializing the app instance.
In NestJS, you create a testing module that mimics your real app module. Then you create an app instance from it and start it in memory. This lets you send requests to it without running a real server.
Result
You can start your NestJS app inside tests and send requests to it.
Understanding how to create a test app instance is key to running E2E tests without a real server.
3
IntermediateUsing Supertest to Send HTTP Requests
🤔Before reading on: Do you think supertest sends requests over the network or directly to the app instance? Commit to your answer.
Concept: Supertest sends HTTP requests directly to the NestJS app instance in memory, not over the network.
Supertest is a library that lets you write code like request(app.getHttpServer()).get('/path').expect(200). It sends HTTP requests to the app instance created in tests and checks the responses easily.
Result
You can write tests that send GET, POST, PUT, DELETE requests and check status codes and response bodies.
Knowing supertest works directly with the app instance makes tests faster and more reliable than real network calls.
4
IntermediateWriting Basic E2E Test Cases
🤔Before reading on: Do you think E2E tests should check only status codes or also response content? Commit to your answer.
Concept: E2E tests check both HTTP status codes and response content to verify correct app behavior.
A basic E2E test sends a request and expects a status code like 200. It also checks the response body to confirm the app returns the right data. For example, testing a GET /users endpoint to return a list of users.
Result
You can write tests that confirm your app returns correct data and status for various endpoints.
Checking response content ensures your app not only responds but responds correctly.
5
IntermediateHandling Setup and Teardown in Tests
🤔
Concept: Learn to prepare test data before tests and clean up after to keep tests independent.
Use beforeAll or beforeEach hooks to set up data or app state before tests run. Use afterAll or afterEach to clean up. This prevents tests from affecting each other and keeps results reliable.
Result
Your tests run consistently and independently without leftover data causing false results.
Managing test lifecycle prevents flaky tests and ensures accurate results.
6
AdvancedTesting with Authentication and Guards
🤔Before reading on: Do you think E2E tests should bypass authentication or test it fully? Commit to your answer.
Concept: E2E tests should include authentication flows to test real user access control.
To test protected routes, your E2E tests must simulate login or provide tokens. This checks if guards and authentication work correctly. For example, first send a login request to get a token, then use it in headers for protected endpoints.
Result
You can test secure parts of your app and confirm only authorized users access them.
Including authentication in E2E tests ensures your app’s security features work as intended.
7
AdvancedMocking External Services in E2E Tests
🤔
Concept: Learn to replace real external services with mocks to isolate tests and avoid side effects.
Sometimes your app calls external APIs or databases. In E2E tests, you can mock these calls to return fixed data. This keeps tests fast and stable. For example, mock a payment gateway response instead of calling the real service.
Result
Your E2E tests run reliably without depending on external systems.
Mocking external services balances test realism with speed and reliability.
8
ExpertOptimizing E2E Tests for Speed and Reliability
🤔Before reading on: Do you think running all E2E tests in one big batch is best, or splitting them smartly? Commit to your answer.
Concept: Experts organize E2E tests to run fast and avoid flaky failures by isolating tests and parallelizing where possible.
Large E2E test suites can be slow. Experts split tests by feature, use test databases reset between runs, and run tests in parallel. They also use retries and detailed logging to diagnose failures quickly.
Result
Your E2E tests run efficiently and provide clear feedback, even in big projects.
Optimizing test structure and environment is crucial for maintaining fast, trustworthy E2E tests in production.
Under the Hood
Supertest works by wrapping your NestJS app's HTTP server instance and sending simulated HTTP requests directly to it in memory. This avoids real network calls, making tests faster and more controlled. NestJS creates a test module that mimics the real app, including controllers, services, and middleware. When a test sends a request, NestJS processes it through the full request lifecycle, including routing, guards, pipes, and interceptors, just like in production. The response is captured and returned to the test code for assertions.
Why designed this way?
This design allows fast, reliable tests without needing a real network or server process. It avoids flakiness caused by network issues and speeds up test runs. Alternatives like spinning up a real server or using HTTP clients over the network are slower and less reliable. NestJS’s modular architecture makes it easy to create isolated test modules that behave like the real app but run entirely in memory.
┌───────────────┐
│ Test Code     │
│ (supertest)   │
└──────┬────────┘
       │ sends HTTP request
       ▼
┌───────────────┐
│ NestJS Test   │
│ App Instance  │
│ (in-memory)   │
└──────┬────────┘
       │ processes request
       ▼
┌───────────────┐
│ Controllers & │
│ Middleware    │
│ Services      │
└──────┬────────┘
       │ sends HTTP response
       ▼
┌───────────────┐
│ Test Code     │
│ receives resp │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do you think E2E tests only check if endpoints respond, ignoring internal logic? Commit yes or no.
Common Belief:E2E tests just check if endpoints respond with status codes, not the internal app logic.
Tap to reveal reality
Reality:E2E tests run the full app logic including controllers, services, guards, and middleware, verifying the entire request lifecycle.
Why it matters:Ignoring internal logic means missing bugs in business rules or security that only show up when the full app runs.
Quick: Do you think supertest sends requests over the real network? Commit yes or no.
Common Belief:Supertest sends HTTP requests over the network like a browser or Postman.
Tap to reveal reality
Reality:Supertest sends requests directly to the app instance in memory without network overhead.
Why it matters:Thinking it uses the network leads to slower tests and confusion about test isolation.
Quick: Do you think E2E tests should always mock external services? Commit yes or no.
Common Belief:E2E tests must always use real external services to be accurate.
Tap to reveal reality
Reality:Mocking external services in E2E tests is common to keep tests fast and reliable, while still testing app integration.
Why it matters:Using real external services can cause flaky tests due to network issues or service downtime.
Quick: Do you think E2E tests replace unit tests completely? Commit yes or no.
Common Belief:E2E tests are enough; unit tests are unnecessary if E2E tests exist.
Tap to reveal reality
Reality:E2E tests complement but do not replace unit tests; unit tests catch small bugs faster and isolate logic.
Why it matters:Relying only on E2E tests can slow development and make debugging harder.
Expert Zone
1
E2E tests can reveal integration issues caused by middleware order or global pipes that unit tests miss.
2
Properly resetting the test database state between tests is crucial to avoid hidden dependencies and flaky failures.
3
Using supertest with NestJS’s fastify adapter requires special setup because of different server internals.
When NOT to use
E2E testing with supertest is not ideal for testing isolated business logic or small units of code; use unit tests instead. Also, for UI behavior, use dedicated frontend testing tools like Cypress. When external services are complex and slow, consider contract testing or integration tests instead of full E2E.
Production Patterns
In real projects, E2E tests run in CI pipelines on every code push to catch regressions early. Teams organize tests by feature areas and use test databases reset via migrations or fixtures. Authentication flows are tested by automating login and token handling. External APIs are mocked with tools like nock or custom mocks to keep tests stable.
Connections
Unit Testing
complements
Understanding E2E testing helps appreciate why unit tests focus on small parts, while E2E tests check the whole app working together.
Continuous Integration (CI)
builds-on
E2E tests are often run automatically in CI pipelines to ensure code changes don’t break the app, linking testing to deployment safety.
Restaurant Order Process
similar process flow
Seeing E2E testing as a full order-to-delivery process helps understand the importance of testing every step in a system.
Common Pitfalls
#1Running E2E tests without resetting the database causes tests to interfere with each other.
Wrong approach:beforeAll(() => { // no database cleanup }); it('test 1', () => { /* test code */ }); it('test 2', () => { /* test code */ });
Correct approach:beforeEach(async () => { await resetDatabase(); }); it('test 1', () => { /* test code */ }); it('test 2', () => { /* test code */ });
Root cause:Not isolating test data causes leftover data to affect later tests, leading to flaky failures.
#2Skipping authentication in E2E tests for protected routes leads to false positives.
Wrong approach:request(app.getHttpServer()).get('/protected').expect(200); // no auth token
Correct approach:const token = await loginAndGetToken(); request(app.getHttpServer()).get('/protected').set('Authorization', `Bearer ${token}`).expect(200);
Root cause:Ignoring auth means tests don’t verify real access control, missing security bugs.
#3Using real external APIs in E2E tests causes slow and unreliable tests.
Wrong approach:appService.callRealPaymentGateway(); // in E2E test
Correct approach:jest.mock('paymentGateway', () => ({ processPayment: () => Promise.resolve({ success: true }) }));
Root cause:External dependencies introduce network delays and failures, making tests flaky.
Key Takeaways
E2E testing with supertest checks your entire NestJS app by simulating real user HTTP requests and verifying responses.
Supertest sends requests directly to the app instance in memory, making tests fast and reliable without real network calls.
Writing good E2E tests means checking both status codes and response content, including authentication and protected routes.
Managing test setup and teardown prevents flaky tests by keeping each test independent and isolated.
Experts optimize E2E tests by mocking external services, organizing tests smartly, and running them efficiently in CI pipelines.

Practice

(1/5)
1. What is the main purpose of using supertest in NestJS E2E testing?
easy
A. To style the frontend components
B. To mock database calls inside unit tests
C. To compile TypeScript code before running tests
D. To simulate HTTP requests and test the whole app behavior

Solution

  1. Step 1: Understand E2E testing goal

    E2E testing checks the entire app by simulating real HTTP requests and responses.
  2. Step 2: Role of supertest in NestJS

    Supertest helps send HTTP requests to the running NestJS app and verify responses, enabling full app behavior testing.
  3. Final Answer:

    To simulate HTTP requests and test the whole app behavior -> Option D
  4. Quick Check:

    E2E testing = simulate HTTP requests [OK]
Hint: Remember: supertest sends HTTP requests to test app end-to-end [OK]
Common Mistakes:
  • Confusing supertest with mocking libraries
  • Thinking supertest is for styling or compiling
  • Using supertest only for unit tests
2. Which of the following is the correct way to import and use supertest in a NestJS E2E test file?
easy
A. const request = import('supertest');
B. import * as request from 'supertest';
C. import request from 'supertest';
D. const request = require('supertest');

Solution

  1. Step 1: Check ES module import style

    In modern NestJS with ES modules, import * as request from 'supertest'; is the correct syntax to import all exports as an object.
  2. Step 2: Identify incorrect options

    const request = require('supertest'); uses CommonJS require, which is discouraged in ES module projects. import request from 'supertest'; assumes default export which supertest does not have. const request = import('supertest'); is invalid syntax.
  3. Final Answer:

    import * as request from 'supertest'; -> Option B
  4. Quick Check:

    Use ES module import * as for supertest [OK]
Hint: Use import * as request from 'supertest' in NestJS E2E tests [OK]
Common Mistakes:
  • Using require() instead of import
  • Assuming supertest has a default export
  • Using dynamic import incorrectly
3. Given this NestJS E2E test snippet, what will be the HTTP status code returned by the test?
const app = await NestFactory.create(AppModule);
await app.init();
return request(app.getHttpServer())
  .get('/users')
  .expect(200);
medium
A. 200
B. 404
C. 500
D. 301

Solution

  1. Step 1: Analyze the test request

    The test sends a GET request to '/users' on the initialized app and expects status 200.
  2. Step 2: Understand .expect(200) behavior

    The .expect(200) asserts the response status must be 200 for the test to pass.
  3. Final Answer:

    200 -> Option A
  4. Quick Check:

    Test expects status 200, so response = 200 [OK]
Hint: Check the .expect() status code to predict response [OK]
Common Mistakes:
  • Confusing expected status with actual response
  • Assuming 404 if route not defined without context
  • Ignoring .expect() assertion meaning
4. Identify the error in this NestJS E2E test code snippet:
const app = await NestFactory.create(AppModule);
return request(app.getHttpServer())
  .post('/login')
  .send({ username: 'user', password: 'pass' })
  .expect(201);
await app.close();
medium
A. Using .send() with POST request is invalid
B. Missing await before request call
C. Calling app.close() after return statement
D. Expecting status 201 instead of 200

Solution

  1. Step 1: Check code flow with return and await

    The return statement ends the function immediately, so await app.close() after return is unreachable and never runs.
  2. Step 2: Identify proper app closing

    To close the app properly, await app.close() must be called before returning the request promise.
  3. Final Answer:

    Calling app.close() after return statement -> Option C
  4. Quick Check:

    Code after return is unreachable [OK]
Hint: Place app.close() before return to ensure cleanup [OK]
Common Mistakes:
  • Ignoring unreachable code after return
  • Thinking .send() is invalid for POST
  • Assuming status 201 is always wrong
5. You want to write an E2E test in NestJS that sends a POST request with JSON data, verifies the response body contains a specific property, and then closes the app properly. Which code snippet correctly implements this?
hard
A. const app = await NestFactory.create(AppModule); await app.init(); const response = await request(app.getHttpServer()) .post('/items') .send({ name: 'item1' }) .expect(201); expect(response.body).toHaveProperty('id'); await app.close();
B. const app = await NestFactory.create(AppModule); const response = request(app.getHttpServer()) .post('/items') .send({ name: 'item1' }) .expect(201); expect(response.body).toHaveProperty('id'); await app.close();
C. const app = await NestFactory.create(AppModule); await app.init(); request(app.getHttpServer()) .post('/items') .send({ name: 'item1' }) .expect(201) .then(response => { expect(response.body).toHaveProperty('id'); }); await app.close();
D. const app = await NestFactory.create(AppModule); await app.init(); const response = await request(app.getHttpServer()) .post('/items') .send({ name: 'item1' }) .expect(200); expect(response.body).toHaveProperty('id'); await app.close();

Solution

  1. Step 1: Initialize and start the app

    Await NestFactory.create(AppModule) and app.init() to fully bootstrap and start the HTTP server before sending requests.
  2. Step 2: Send POST request and await response

    Await the supertest chain request(app.getHttpServer()).post('/items').send({ name: 'item1' }).expect(201) to capture the response object.
  3. Step 3: Verify response body and close app

    Assert expect(response.body).toHaveProperty('id') then await app.close() for proper cleanup.
  4. Final Answer:

    The snippet with await app.init(), await request.expect(201), body check, and await app.close() -> Option A
  5. Quick Check:

    Await init, await request, assert body, await close [OK]
Hint: Always await app.init(), request, and app.close() in E2E tests [OK]
Common Mistakes:
  • Not awaiting app.init() before requests
  • Not awaiting request call causing missing response
  • Expecting wrong status code (200 vs 201)
  • Calling app.close() too early or after return
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