> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

# Chapter 7: Testing

> Testing is essential for building reliable, maintainable applications. This chapter covers unit, integration, and end-to-end (E2E) testing in NestJS, including mocking strategies, test utilities, CI/CD integration, and best practices. We'll walk through practical examples and explain how to build a robust test suite.

***

## 7.1 Why Test?

Testing provides numerous benefits that make it essential for production applications.

### Benefits of Testing

**Catch bugs early:**

* Find issues before they reach production
* Reduce debugging time
* Prevent regressions

**Refactor with confidence:**

* Make changes knowing tests will catch breakages
* Improve code quality without fear
* Enable continuous refactoring

**Ensure business logic correctness:**

* Verify requirements are met
* Validate edge cases
* Confirm expected behavior

**Enable continuous delivery:**

* Deploy with confidence
* Automate quality checks
* Reduce manual testing

**Document expected behavior:**

* Tests serve as living documentation
* Show how code should be used
* Provide examples for other developers

**Analogy:**

> Think of tests as a building's fire alarm system. You do not install fire alarms because you expect fires every day -- you install them so that when something goes wrong, you know immediately, before the whole building burns down. Unit tests are smoke detectors in individual rooms (catch small problems fast). Integration tests are sprinkler systems that test whether multiple rooms work together. E2E tests are full fire drills that simulate real emergencies end-to-end. The teams that skip testing are the ones debugging production incidents at 3 AM.

### Testing Pyramid

```text theme={null}
        /\
       /  \      E2E Tests (Few)
      /____\
     /      \     Integration Tests (Some)
    /________\
   /          \   Unit Tests (Many)
  /____________\
```

**Unit Tests (Base):**

* Fast, isolated, many
* Test individual components
* Mock all dependencies

**Integration Tests (Middle):**

* Test component interactions
* Use real dependencies where possible
* Moderate speed

**E2E Tests (Top):**

* Test full user flows
* Use real everything
* Slow but comprehensive

### Testing Strategy Comparison

| Aspect              | Unit Tests                              | Integration Tests                       | E2E Tests                               |
| ------------------- | --------------------------------------- | --------------------------------------- | --------------------------------------- |
| **What's Tested**   | Single class in isolation               | Multiple classes together               | Full HTTP request through entire stack  |
| **Dependencies**    | All mocked                              | Some real, some mocked                  | All real (or containerized)             |
| **Speed**           | Fast (1-5ms each)                       | Moderate (50-500ms each)                | Slow (500ms-5s each)                    |
| **Database**        | Never touched                           | In-memory or test DB                    | Real test database                      |
| **Catches**         | Logic errors in one class               | Integration errors, DI wiring           | Full-stack regressions, HTTP contract   |
| **Confidence**      | Narrow (this function works)            | Medium (these components work together) | Broad (the API works as clients expect) |
| **Maintenance**     | Low (changes are local)                 | Medium                                  | High (brittle if API changes)           |
| **When They Break** | Tells you exactly which function failed | Tells you which interaction failed      | Tells you which endpoint failed         |
| **Test Count**      | Many (70% of tests)                     | Some (20% of tests)                     | Few (10% of tests)                      |

**Decision Framework -- What Should I Test and How?**

| Code Under Test                                | Test Type                             | Why                                             |
| ---------------------------------------------- | ------------------------------------- | ----------------------------------------------- |
| Service method with business logic             | Unit test with mocked repository      | Fast, tests the logic in isolation              |
| Service + Repository working together          | Integration test with in-memory DB    | Catches ORM/query issues                        |
| Full API endpoint (auth, validation, response) | E2E test with supertest               | Catches HTTP-level issues                       |
| Guard or interceptor logic                     | Unit test with mock ExecutionContext  | Guards are pure logic, test them like functions |
| Complex database query                         | Integration test with real DB         | ORM-generated SQL may differ from expectation   |
| Validation DTO behavior                        | Unit test (instantiate DTO, validate) | Fast, catches decorator configuration issues    |

<Tip>
  **The 80/20 Rule for NestJS Testing**: If you only have time for one type of test per feature, write an integration test for the service (real DI, mocked DB) and an E2E test for the happy path. These two tests catch 80% of real-world bugs. Add unit tests for complex business logic and edge cases.
</Tip>

***

## 7.2 Unit Testing

Unit tests verify individual components (services, controllers) in isolation. They should be fast and not depend on external systems.

### Setting Up Tests

NestJS comes with Jest configured by default. Test files should end with `.spec.ts` and live next to the file they test (not in a separate `__tests__` directory). This co-location makes it obvious which code has tests and which does not.

The key utility is `Test.createTestingModule()` -- it creates a mini NestJS application context for your test, wiring up DI exactly like the real app. This means you can swap real providers for mocks and test components in isolation while still using NestJS's DI system.

```typescript theme={null}
// users/users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';

describe('UsersService', () => {
  let service: UsersService;

  beforeEach(async () => {
    // Test.createTestingModule() mirrors @Module() -- it accepts the same
    // metadata (providers, controllers, imports). The key difference: you
    // control exactly what goes in, letting you substitute mocks for real
    // databases, HTTP clients, etc.
    const module: TestingModule = await Test.createTestingModule({
      providers: [UsersService],
    }).compile();  // .compile() resolves all dependencies and creates instances

    // module.get() retrieves a provider from the test DI container.
    // This is the same instance that would be injected into a controller.
    service = module.get<UsersService>(UsersService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });
});
```

**Practical Tip:** If `Test.createTestingModule().compile()` throws "Nest can't resolve dependencies," you are missing a provider or mock in the test module. Check that every dependency in the constructor is either provided directly or mocked.

### Basic Service Test

```typescript theme={null}
// users/users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { UserRepository } from './user.repository';

describe('UsersService', () => {
  let service: UsersService;
  let repository: UserRepository;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        {
          provide: UserRepository,
          useValue: {
            findAll: jest.fn(),
            findOne: jest.fn(),
            create: jest.fn(),
            update: jest.fn(),
            remove: jest.fn(),
          },
        },
      ],
    }).compile();

    service = module.get<UsersService>(UsersService);
    repository = module.get<UserRepository>(UserRepository);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('findAll', () => {
    it('should return an array of users', async () => {
      const users = [{ id: 1, name: 'John' }];
      jest.spyOn(repository, 'findAll').mockResolvedValue(users);

      const result = await service.findAll();

      expect(result).toEqual(users);
      expect(repository.findAll).toHaveBeenCalled();
    });
  });

  describe('findOne', () => {
    it('should return a user', async () => {
      const user = { id: 1, name: 'John' };
      jest.spyOn(repository, 'findOne').mockResolvedValue(user);

      const result = await service.findOne(1);

      expect(result).toEqual(user);
      expect(repository.findOne).toHaveBeenCalledWith(1);
    });

    it('should throw if user not found', async () => {
      jest.spyOn(repository, 'findOne').mockResolvedValue(null);

      await expect(service.findOne(999)).rejects.toThrow('User not found');
    });
  });

  describe('create', () => {
    it('should create a user', async () => {
      const dto = { name: 'John', email: 'john@example.com' };
      const user = { id: 1, ...dto };
      jest.spyOn(repository, 'create').mockResolvedValue(user);

      const result = await service.create(dto);

      expect(result).toEqual(user);
      expect(repository.create).toHaveBeenCalledWith(dto);
    });
  });
});
```

**Diagram: Unit Test Flow**

```text theme={null}
Test Setup
    ↓
Mock Dependencies
    ↓
Create Service Instance
    ↓
Execute Test
    ↓
Assert Results
    ↓
Cleanup
```

***

## 7.3 Mocking Dependencies

Mocking dependencies isolates the unit under test and makes tests fast and predictable.

### Mocking Services

```typescript theme={null}
const mockUserRepository = {
  findAll: jest.fn(),
  findOne: jest.fn(),
  create: jest.fn(),
  update: jest.fn(),
  remove: jest.fn(),
};

const module = await Test.createTestingModule({
  providers: [
    UsersService,
    {
      provide: UserRepository,
      useValue: mockUserRepository,
    },
  ],
}).compile();
```

### Mocking with Partial

```typescript theme={null}
const mockUserRepository: Partial<UserRepository> = {
  findAll: jest.fn().mockResolvedValue([]),
  findOne: jest.fn().mockResolvedValue(null),
};

const module = await Test.createTestingModule({
  providers: [
    UsersService,
    {
      provide: UserRepository,
      useValue: mockUserRepository,
    },
  ],
}).compile();
```

### Mocking Modules

```typescript theme={null}
const module = await Test.createTestingModule({
  providers: [UsersService],
})
  .overrideModule(UsersModule)
  .useModule(TestUsersModule)
  .compile();
```

### Mocking with jest.fn()

```typescript theme={null}
describe('UsersService', () => {
  let service: UsersService;
  let repository: jest.Mocked<UserRepository>;

  beforeEach(async () => {
    const mockRepository = {
      findAll: jest.fn(),
      findOne: jest.fn(),
      create: jest.fn(),
    };

    const module = await Test.createTestingModule({
      providers: [
        UsersService,
        {
          provide: UserRepository,
          useValue: mockRepository,
        },
      ],
    }).compile();

    service = module.get(UsersService);
    repository = module.get(UserRepository);
  });

  it('should find all users', async () => {
    const users = [{ id: 1, name: 'John' }];
    repository.findAll.mockResolvedValue(users);

    const result = await service.findAll();

    expect(result).toEqual(users);
    expect(repository.findAll).toHaveBeenCalledTimes(1);
  });
});
```

**Tip:** Use mocks for unit tests to isolate components. Use real dependencies for integration tests to verify actual interactions.

**Common Mistake:** Over-mocking to the point where tests only verify that mock methods were called, not that the actual logic works. If your test does nothing but check `expect(repository.create).toHaveBeenCalledWith(dto)`, you are testing the wiring, not the behavior. A good unit test verifies the *output* given specific inputs, and uses mocks only to control the environment.

**Another Mistake:** Forgetting `jest.clearAllMocks()` in `beforeEach`. Without it, mock call counts and return values leak between tests, causing flaky failures that are maddening to debug.

***

## 7.4 Testing Controllers

Controllers handle HTTP requests and should be tested with mocked services.

### Controller Test Example

```typescript theme={null}
// users/users.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

describe('UsersController', () => {
  let controller: UsersController;
  let service: UsersService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [UsersController],
      providers: [
        {
          provide: UsersService,
          useValue: {
            findAll: jest.fn(),
            findOne: jest.fn(),
            create: jest.fn(),
            update: jest.fn(),
            remove: jest.fn(),
          },
        },
      ],
    }).compile();

    controller = module.get<UsersController>(UsersController);
    service = module.get<UsersService>(UsersService);
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });

  describe('findAll', () => {
    it('should return an array of users', async () => {
      const users = [{ id: 1, name: 'John' }];
      jest.spyOn(service, 'findAll').mockResolvedValue(users);

      const result = await controller.findAll();

      expect(result).toEqual(users);
      expect(service.findAll).toHaveBeenCalled();
    });
  });

  describe('create', () => {
    it('should create a user', async () => {
      const dto = { name: 'John', email: 'john@example.com' };
      const user = { id: 1, ...dto };
      jest.spyOn(service, 'create').mockResolvedValue(user);

      const result = await controller.create(dto);

      expect(result).toEqual(user);
      expect(service.create).toHaveBeenCalledWith(dto);
    });
  });
});
```

***

## 7.5 Integration Testing

Integration tests verify how components work together (e.g., service + database). They are slower than unit tests but catch more real-world issues.

### Setting Up Integration Tests

```typescript theme={null}
// users/users.integration.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';

describe('UsersService Integration', () => {
  let service: UsersService;
  let module: TestingModule;

  beforeAll(async () => {
    module = await Test.createTestingModule({
      imports: [
        TypeOrmModule.forRoot({
          type: 'sqlite',
          database: ':memory:',
          entities: [User],
          synchronize: true,
        }),
        TypeOrmModule.forFeature([User]),
      ],
      providers: [UsersService],
    }).compile();

    service = module.get<UsersService>(UsersService);
  });

  afterAll(async () => {
    await module.close();
  });

  beforeEach(async () => {
    // Clean database before each test
    const repository = module.get(getRepositoryToken(User));
    await repository.clear();
  });

  it('should create a user', async () => {
    const dto = { name: 'John', email: 'john@example.com' };
    const user = await service.create(dto);

    expect(user).toBeDefined();
    expect(user.name).toBe(dto.name);
    expect(user.email).toBe(dto.email);
  });

  it('should find all users', async () => {
    await service.create({ name: 'John', email: 'john@example.com' });
    await service.create({ name: 'Jane', email: 'jane@example.com' });

    const users = await service.findAll();

    expect(users).toHaveLength(2);
  });
});
```

**Tip:** Use an in-memory database (e.g., SQLite) for fast, isolated integration tests. Clean up data between tests to avoid side effects.

***

## 7.6 End-to-End (E2E) Testing

E2E tests simulate real user scenarios by making HTTP requests to your app. They test the entire stack, from HTTP layer to database.

### Setting Up E2E Tests

```typescript theme={null}
// test/app.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';

describe('AppController (e2e)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  afterAll(async () => {
    await app.close();
  });

  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(200)
      .expect('Hello World!');
  });
});
```

### E2E Test with Authentication

```typescript theme={null}
// auth/auth.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';

describe('Auth (e2e)', () => {
  let app: INestApplication;
  let accessToken: string;

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  afterAll(async () => {
    await app.close();
  });

  describe('POST /auth/register', () => {
    it('should register a new user', () => {
      return request(app.getHttpServer())
        .post('/auth/register')
        .send({
          name: 'John Doe',
          email: 'john@example.com',
          password: 'Password123!',
        })
        .expect(201)
        .expect((res) => {
          expect(res.body).toHaveProperty('id');
          expect(res.body.email).toBe('john@example.com');
        });
    });
  });

  describe('POST /auth/login', () => {
    it('should login and return access token', async () => {
      const response = await request(app.getHttpServer())
        .post('/auth/login')
        .send({
          email: 'john@example.com',
          password: 'Password123!',
        })
        .expect(200);

      expect(response.body).toHaveProperty('access_token');
      accessToken = response.body.access_token;
    });
  });

  describe('GET /auth/profile', () => {
    it('should get user profile with valid token', () => {
      return request(app.getHttpServer())
        .get('/auth/profile')
        .set('Authorization', `Bearer ${accessToken}`)
        .expect(200)
        .expect((res) => {
          expect(res.body).toHaveProperty('email');
        });
    });

    it('should return 401 without token', () => {
      return request(app.getHttpServer())
        .get('/auth/profile')
        .expect(401);
    });
  });
});
```

**Diagram: E2E Test Flow**

```text theme={null}
Test Setup
    ↓
Create App Instance
    ↓
HTTP Request (supertest)
    ↓
App Processes Request
    ↓
Controller → Service → Repository → Database
    ↓
Response
    ↓
Assert Response
    ↓
Cleanup
```

***

## 7.7 Testing Async Operations

Handle async operations properly in tests.

### Testing Promises

```typescript theme={null}
it('should handle async operations', async () => {
  const result = await service.findAll();
  expect(result).toBeDefined();
});

// Or using resolves/rejects
it('should resolve with data', async () => {
  await expect(service.findAll()).resolves.toEqual([]);
});

it('should reject with error', async () => {
  await expect(service.findOne(999)).rejects.toThrow('Not found');
});
```

### Testing Observables

```typescript theme={null}
import { firstValueFrom } from 'rxjs';

it('should handle observables', async () => {
  const result = await firstValueFrom(service.getStream());
  expect(result).toBeDefined();
});
```

***

## 7.8 Test Utilities

NestJS provides utilities to make testing easier.

### Override Provider

```typescript theme={null}
const module = await Test.createTestingModule({
  providers: [UsersService, UserRepository],
})
  .overrideProvider(UserRepository)
  .useValue(mockRepository)
  .compile();
```

### Override Guard

This is one of the most useful testing patterns in NestJS. When testing a controller, you usually do not want to deal with real authentication -- you just want to test the route logic. Overriding the guard with a simple `canActivate: () => true` lets every request through.

```typescript theme={null}
const module = await Test.createTestingModule({
  controllers: [UsersController],
  providers: [UsersService],
})
  .overrideGuard(JwtAuthGuard)
  // Replace the real JWT guard with one that always allows access.
  // For testing protected routes, you can also attach a fake user:
  // canActivate: (ctx) => { ctx.switchToHttp().getRequest().user = mockUser; return true; }
  .useValue({ canActivate: () => true })
  .compile();
```

**Practical Tip:** If your controller expects `request.user` to be populated (because the guard normally sets it), your override guard must also set it. Otherwise, your handler will get `undefined` when it accesses `req.user`, and you will spend 20 minutes wondering why your test fails.

### Override Interceptor

```typescript theme={null}
const module = await Test.createTestingModule({
  providers: [UsersService],
})
  .overrideInterceptor(LoggingInterceptor)
  .useValue({ intercept: () => {} })
  .compile();
```

***

## 7.9 Testing Edge Cases

**Edge Case 1: Testing request-scoped providers**

Request-scoped providers create a new instance per request, but `Test.createTestingModule()` creates singleton instances by default. To test request-scoped behavior, you need to use `module.resolve()` instead of `module.get()` -- `resolve()` creates a new instance each time, mimicking request scope.

```typescript theme={null}
// module.get() returns the same instance every time (singleton behavior)
const service1 = module.get(RequestScopedService); // Instance A
const service2 = module.get(RequestScopedService); // Instance A (same)

// module.resolve() creates a new instance each time (request-scope behavior)
const service1 = await module.resolve(RequestScopedService); // Instance A
const service2 = await module.resolve(RequestScopedService); // Instance B (different)
```

**Edge Case 2: Testing guards that depend on `request.user`**

When you override a guard in tests, the guard's `canActivate()` method no longer runs -- which means it no longer attaches the user to the request. If your controller reads `req.user`, you need your mock guard to set it:

```typescript theme={null}
.overrideGuard(JwtAuthGuard)
.useValue({
  canActivate: (ctx: ExecutionContext) => {
    const req = ctx.switchToHttp().getRequest();
    req.user = { id: 1, email: 'test@test.com', roles: ['admin'] };
    return true;
  },
})
```

**Edge Case 3: E2E tests with database state leaking between tests**

If Test A creates a user and Test B assumes an empty database, Test B fails when run after Test A. Solutions: (1) Truncate all tables in `beforeEach` (fast but requires careful ordering for foreign keys); (2) Use database transactions -- start a transaction in `beforeEach`, roll it back in `afterEach` (no data persists, extremely fast); (3) Use unique test data with random IDs so tests never collide.

**Edge Case 4: Testing interceptors that use RxJS operators**

Interceptors return Observables, which means testing them requires subscribing or converting to promises. Use `firstValueFrom()` from `rxjs` to convert the Observable to a promise in your test:

```typescript theme={null}
import { firstValueFrom, of } from 'rxjs';

it('should transform response', async () => {
  const interceptor = new TransformInterceptor();
  const mockHandler = { handle: () => of({ name: 'John' }) };
  const mockContext = {} as ExecutionContext;

  const result$ = interceptor.intercept(mockContext, mockHandler);
  const result = await firstValueFrom(result$);

  expect(result).toEqual({ success: true, data: { name: 'John' }, timestamp: expect.any(String) });
});
```

***

## 7.9 CI/CD Integration

Automate tests in your CI pipeline to ensure code is always tested before deployment.

### GitHub Actions Workflow

```yaml theme={null}
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:14
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run unit tests
        run: npm run test

      - name: Run E2E tests
        run: npm run test:e2e
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb

      - name: Generate coverage
        run: npm run test:cov

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info
```

### Test Scripts

```json theme={null}
// package.json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  }
}
```

***

## 7.10 Best Practices

Following best practices ensures your tests are maintainable and effective.

### Test Organization

**Structure tests by feature:**

```
users/
├── users.service.ts
├── users.service.spec.ts
├── users.controller.ts
├── users.controller.spec.ts
└── users.integration.spec.ts
```

### Naming Conventions

```typescript theme={null}
describe('UsersService', () => {
  describe('findAll', () => {
    it('should return an array of users', () => {});
    it('should return empty array when no users exist', () => {});
  });
});
```

### Test Isolation

Each test should be independent:

```typescript theme={null}
beforeEach(async () => {
  // Reset mocks
  jest.clearAllMocks();
  
  // Clean database
  await repository.clear();
});
```

### Use Descriptive Test Names

```typescript theme={null}
// Bad
it('works', () => {});

// Good
it('should return user when valid ID is provided', () => {});
```

### Test Edge Cases

```typescript theme={null}
describe('findOne', () => {
  it('should return user for valid ID', () => {});
  it('should throw error for invalid ID', () => {});
  it('should throw error for non-existent user', () => {});
  it('should handle null ID', () => {});
});
```

### Keep Tests Fast

* Use mocks for unit tests
* Use in-memory databases for integration tests
* Run E2E tests separately
* Use test parallelization

### Coverage Goals

Aim for:

* **80%+ coverage** for business logic
* **100% coverage** for critical paths
* Focus on quality over quantity

### Clean Up Resources

```typescript theme={null}
afterAll(async () => {
  await app.close();
  await databaseConnection.close();
});
```

### Best Practices Checklist

* Write tests for all business logic
* Use mocks for unit tests, real dependencies for integration/E2E
* Keep tests fast and isolated
* Use coverage reports to identify gaps
* Run tests on every commit and pull request
* Name tests clearly and organize by feature
* Clean up resources after each test
* Test edge cases and error scenarios
* Keep test code maintainable
* Use descriptive test names

***

## 7.11 Summary

You've learned how to test NestJS applications at every level:

**Key Concepts:**

* **Unit Tests**: Test individual components in isolation
* **Integration Tests**: Test component interactions
* **E2E Tests**: Test full user flows
* **Mocking**: Isolate components for testing
* **CI/CD**: Automate testing in pipelines

**Best Practices:**

* Write tests for all business logic
* Use mocks for unit tests
* Keep tests fast and isolated
* Test edge cases
* Maintain high coverage
* Clean up resources

**Next Chapter:** Learn about microservices architecture, message brokers, and distributed systems with NestJS.
