# Run Node.js tests in a container


## Prerequisites

Complete all the previous sections of this guide, starting with [Containerize a Node.js application](/guides/nodejs/run-tests/containerize/).

## Overview

Testing is a core part of building reliable software. Whether you're writing unit tests, integration tests, or end-to-end tests, running them consistently across environments matters. Docker makes this easy by giving you the same setup locally, in CI/CD, and during image builds.

## Run tests when developing locally

The sample application uses Vitest for testing, and it already includes tests for React components, custom hooks, API routes, database operations, and utility functions.

### Run tests locally (without Docker)

```console
$ npm run test
```

### Add test service to Docker Compose

To run tests in a containerized environment, you need to add a dedicated test service to your `compose.yml` file. Add the following service configuration:

```yaml
services:
  # ... existing services ...

  # ========================================
  # Test Service
  # ========================================
  app-test:
    build:
      context: .
      dockerfile: Dockerfile
      target: test
    container_name: todoapp-test
    environment:
      NODE_ENV: test
      POSTGRES_HOST: db
      POSTGRES_PORT: 5432
      POSTGRES_DB: todoapp_test
      POSTGRES_USER: todoapp
      POSTGRES_PASSWORD: '${POSTGRES_PASSWORD:-todoapp_password}'
    depends_on:
      db:
        condition: service_healthy
    command: ['npm', 'run', 'test:coverage']
    networks:
      - todoapp-network
    profiles:
      - test
```

This test service configuration:

- **Builds from test stage**: Uses the `test` target from your multi-stage Dockerfile
- **Isolated test database**: Uses a separate `todoapp_test` database for testing
- **Profile-based**: Uses the `test` profile so it only runs when explicitly requested
- **Health dependency**: Waits for the database to be healthy before starting tests

### Run tests in a container

You can run tests using the dedicated test service:

```console
$ docker compose up app-test --build
```

Or run tests against the development service:

```console
$ docker compose run --rm app-dev npm run test
```

For a one-off test run with coverage:

```console
$ docker compose run --rm app-dev npm run test:coverage
```

### Run tests with coverage

To generate a coverage report:

```console
$ npm run test:coverage
```

You should see output like the following:

```console
> docker-nodejs-sample@1.0.0 test
> vitest --run

 ✓ src/server/__tests__/routes/todos.test.ts (5 tests) 16ms
 ✓ src/shared/utils/__tests__/validation.test.ts (15 tests) 6ms
 ✓ src/client/components/__tests__/LoadingSpinner.test.tsx (8 tests) 67ms
 ✓ src/server/database/__tests__/postgres.test.ts (13 tests) 136ms
 ✓ src/client/components/__tests__/ErrorMessage.test.tsx (8 tests) 127ms
 ✓ src/client/components/__tests__/TodoList.test.tsx (8 tests) 147ms
 ✓ src/client/components/__tests__/TodoItem.test.tsx (8 tests) 218ms
 ✓ src/client/__tests__/App.test.tsx (13 tests) 259ms
 ✓ src/client/components/__tests__/AddTodoForm.test.tsx (12 tests) 323ms
 ✓ src/client/hooks/__tests__/useTodos.test.ts (11 tests) 569ms

 Test Files  9 passed (9)
      Tests  88 passed (88)
   Start at  20:57:19
   Duration  4.41s (transform 1.79s, setup 2.66s, collect 5.38s, tests 4.61s, environment 14.07s, prepare 4.34s)
```

### Test structure

The test suite covers:

- **Client Components** (`src/client/components/__tests__/`): React component testing with React Testing Library
- **Custom Hooks** (`src/client/hooks/__tests__/`): React hooks testing with proper mocking
- **Server Routes** (`src/server/__tests__/routes/`): API endpoint testing
- **Database Layer** (`src/server/database/__tests__/`): PostgreSQL database operations testing
- **Utility Functions** (`src/shared/utils/__tests__/`): Validation and helper function testing
- **Integration Tests** (`src/client/__tests__/`): Full application integration testing

## Run tests when building

To run tests during the Docker build process, you need to add a dedicated test stage to your Dockerfile. If you haven't already added this stage, add the following to your multi-stage Dockerfile:

```dockerfile
# ========================================
# Test Stage
# ========================================
FROM build-deps AS test

# Set environment
ENV NODE_ENV=test \
    CI=true

# Copy source files
COPY --chown=nodejs:nodejs . .

# Switch to non-root user
USER nodejs

# Run tests with coverage
CMD ["npm", "run", "test:coverage"]
```

This test stage:

- **Test environment**: Sets `NODE_ENV=test` and `CI=true` for proper test execution
- **Non-root user**: Runs tests as the `nodejs` user for security
- **Flexible execution**: Uses `CMD` instead of `RUN` to allow running tests during build or as a separate container
- **Coverage support**: Configured to run tests with coverage reporting

### Build and run tests during image build

To build an image that runs tests during the build process, you can create a custom Dockerfile or modify the existing one temporarily:

```console
$ docker build --target test -t node-docker-image-test .
```

### Run tests in a dedicated test container

The recommended approach is to use the test service defined in `compose.yml`:

```console
$ docker compose --profile test up app-test --build
```

Or run it as a one-off container:

```console
$ docker compose run --rm app-test
```

### Run tests with coverage in CI/CD

For continuous integration, you can run tests with coverage:

```console
$ docker build --target test --progress=plain --no-cache -t test-image .
$ docker run --rm test-image npm run test:coverage
```

You should see output containing the following:

```console
 ✓ src/server/__tests__/routes/todos.test.ts (5 tests) 16ms
 ✓ src/shared/utils/__tests__/validation.test.ts (15 tests) 6ms
 ✓ src/client/components/__tests__/LoadingSpinner.test.tsx (8 tests) 67ms
 ✓ src/server/database/__tests__/postgres.test.ts (13 tests) 136ms
 ✓ src/client/components/__tests__/ErrorMessage.test.tsx (8 tests) 127ms
 ✓ src/client/components/__tests__/TodoList.test.tsx (8 tests) 147ms
 ✓ src/client/components/__tests__/TodoItem.test.tsx (8 tests) 218ms
 ✓ src/client/__tests__/App.test.tsx (13 tests) 259ms
 ✓ src/client/components/__tests__/AddTodoForm.test.tsx (12 tests) 323ms
 ✓ src/client/hooks/__tests__/useTodos.test.ts (11 tests) 569ms

 Test Files  9 passed (9)
      Tests  88 passed (88)
   Start at  20:57:19
   Duration  4.41s (transform 1.79s, setup 2.66s, collect 5.38s, tests 4.61s, environment 14.07s, prepare 4.34s)
```

## Summary

In this section, you learned how to run tests when developing locally using Docker Compose and how to run tests when building your image.

Related information:

- [Dockerfile reference](/reference/dockerfile/) – Understand all Dockerfile instructions and syntax.
- [Best practices for writing Dockerfiles](/develop/develop-images/dockerfile_best-practices/) – Write efficient, maintainable, and secure Dockerfiles.
- [Compose file reference](/compose/compose-file/) – Learn the full syntax and options available for configuring services in `compose.yaml`.
- [`docker compose run` CLI reference](/reference/cli/docker/compose/run/) – Run one-off commands in a service container.

## Next steps

Next, you’ll learn how to set up a CI/CD pipeline using GitHub Actions.

