content/guides/nodejs/run-tests.md
Complete all the previous sections of this guide, starting with Containerize a Node.js application.
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.
The sample application uses Vitest for testing, and it already includes tests for React components, custom hooks, API routes, database operations, and utility functions.
$ npm run test
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:
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:
test target from your multi-stage Dockerfiletodoapp_test database for testingtest profile so it only runs when explicitly requestedYou can run tests using the dedicated test service:
$ docker compose up app-test --build
Or run tests against the development service:
$ docker compose run --rm app-dev npm run test
For a one-off test run with coverage:
$ docker compose run --rm app-dev npm run test:coverage
To generate a coverage report:
$ npm run test:coverage
You should see output like the following:
> [email protected] 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)
The test suite covers:
src/client/components/__tests__/): React component testing with React Testing Librarysrc/client/hooks/__tests__/): React hooks testing with proper mockingsrc/server/__tests__/routes/): API endpoint testingsrc/server/database/__tests__/): PostgreSQL database operations testingsrc/shared/utils/__tests__/): Validation and helper function testingsrc/client/__tests__/): Full application integration testingTo 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:
# ========================================
# 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:
NODE_ENV=test and CI=true for proper test executionnodejs user for securityCMD instead of RUN to allow running tests during build or as a separate containerTo build an image that runs tests during the build process, you can create a custom Dockerfile or modify the existing one temporarily:
$ docker build --target test -t node-docker-image-test .
The recommended approach is to use the test service defined in compose.yml:
$ docker compose --profile test up app-test --build
Or run it as a one-off container:
$ docker compose run --rm app-test
For continuous integration, you can run tests with coverage:
$ 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:
✓ 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)
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:
compose.yaml.docker compose run CLI reference – Run one-off commands in a service container.Next, you’ll learn how to set up a CI/CD pipeline using GitHub Actions.