How to run E2E Tests with docker-compose
This guide covers using docker-compose to spin up your application, run E2E tests, and then exit with the results.
This post is a good companion to my post NestJS Integration and E2E Tests with TypeORM, PostGres, and JWT.
The TL;DR is:
docker-compose -f docker-compose.e2e.yml up --abort-on-container-exit --exit-code-from app
For the sake of an example, we’ll be testing a hypothetical API written in NodeJS that uses a Postgres database and a Redis instance. We’ll assume tests are run via jest. You can substitute any stack!
To begin, ensure that you have recent versions of docker and docker-compose installed on your machine.
Dockerfile
Start by creating a Dockerfile in your project folder that specifies how to build an image for your app.
The following example is for a NodeJS web API.
FROM node:14.4-alpine As example
# install build dependencies
RUN apk update && apk upgrade
RUN apk add python3 g++ make
# install packages for sending mail (msmtp = sendmail for alpine)
RUN apk add msmtp
RUN ln -sf /usr/bin/msmtp /usr/sbin/sendmail
# make target directory for assigning permissions
RUN mkdir -p /usr/src/app/node_modules
RUN chown -R node:node /usr/src/app
# use target directory
WORKDIR /usr/src/app
# set user
USER node
# copy package*.json separately to prevent re-running npm install with every code change
COPY --chown=node:node package*.json ./
RUN npm install
# copy the project code (e.g. consider: --only=production)
COPY --chown=node:node . .
# expose port 3500
EXPOSE 3500
docker-compose
Create a docker-compose.e2e.yml file.
The following example creates a service called app that runs in the container example, as specified in the Dockerfile.
The command property should specify the command that will run your tests inside the container. In our example, this is yarn test:e2e.
version: '3.8'
services:
app:
container_name: example
build:
context: .
target: example # only build this part of the Dockerfile (see: '... As example' )
volumes:
- .:/usr/src/app
- /usr/src/app/node_modules # 'hack' prevents node_modules/ in the container from being overridden
working_dir: /usr/src/app
command: yarn test:e2e
environment:
PORT: 3500
NODE_ENV: test
DB_HOSTNAME: postgres
DB_PORT: 5432
DB_NAME: example
DB_USERNAME: postgres
DB_PASSWORD: postgres
REDIS_HOSTNAME: redis
REDIS_PORT: 6379
networks:
- webnet
depends_on:
- redis
- postgres
redis:
container_name: redis
image: redis:5
networks:
- webnet
postgres:
container_name: postgres
image: postgres:12
networks:
- webnet
environment:
POSTGRES_DB: example
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
PG_DATA: /var/lib/postgresql/data
volumes:
# - ./seed.db.sql:/docker-entrypoint-initdb.d/db.sql <- run only once when the pgdata volume is first created (when run via docker-compose)
- pgdata:/var/lib/postgresql/data # or specify a local folder like ./docker-volumes/pgdata:/var/lib/postgresql/data
networks:
webnet:
volumes:
pgdata:
logs:
Note how each service shares the same network (called webnet) so they can communicate with each other.
Tip: you can use .env files and reference variables from them in a docker-compose.yml file as follows: ${VARIABLE_NAME}.
If you wish to specify a particular .env file in your docker-compose.yml file:
env_file:
- .env
Run E2E Tests
From your project folder, you can run the following command to run your tests:
docker-compose -f docker-compose.e2e.yml up --abort-on-container-exit --exit-code-from app
The -f flag specifies a custom configuration file for docker-compose. If this is not specified, docker-compose will look for docker-compose.yml by default.
The up command tells docker-compose to bring the containers and services up.
The --abort-on-container-exit and --exit-code-from flags are an important combination.
The first flag shuts things down when our test run is complete, and the second flag will use the exit code from the specified service (in our case the one named app) as the exit code from the overall docker-compose command.
This is a good setup if you have scripts that run tests, or if you have a continuous integration pipeline that automatically runs tests and requires a pass/fail.
Test runners such as jest will generally exit with code 0 (success) if all tests pass, and exit with a non-zero code (failure) if any tests fail.
package.json
If your project uses npm, yarn or similar, you can specify commands to run tests in the scripts section.
Our docker-compose.e2e.yml file requires the app service to run the command: yarn test:e2e. For our hypothetical example app, this is specified as follows:
"test:e2e": "NODE_ENV=test jest --config ./test/jest-e2e.json",
Of course, you will need to specify whatever command initiates running E2E tests that’s particular to your project and environment.
To spin up your application via docker-compose and run tests from your workstation (or CI solution, etc), add the following script:
"test:e2e:docker": "docker-compose -f docker-compose.e2e.yml up --abort-on-container-exit --exit-code-from app"
You can then run via npm run test:e2e:docker or yarn test:e2e:docker to spin up your test environment, run your tests, and exit with the results.
If you are using a different package/dependency management solution, you can specify your test-related scripts there. You also have the option to define shell/bash scripts that can run your tests.