Skip to content

Test via Docker Compose

Run tests inside a container, as part of a docker compose stack.

This is useful for tests when the tests require, e.g. an underlying database, or any additional services.

If you are not sure on what test to use, this is likely the one! Most tests require additional services like a database.

Prerequisites

  • The tests inside the container, by two possible options:

    • Copied into your container build under WORKDIR.
    • Mounted within the docker-compose.yml file under WORKDIR.
  • The testing tool must be installed for the dockerfile USER.
  • The service in the docker-compose.yml must have a TAG_OVERRIDE.

    services:
      api:
        image: "ghcr.io/hotosm/fmtm/backend:${TAG_OVERRIDE:-debug}"
    

    This allows the workflow to inject the tag of the image built for a PR, or during deployment.

  • There should be a .env.example file in the root of your repo, if you require any environment variables to run your service.

    • This file describes all possible environment variables, with examples.
    • The variables in this file are substituted to produce the .env file from Github environment variables.
    SECRET_KEY=${SECRET_KEY:-somesuperdupersecretkeyfortesting}
    ALLOWED_HOSTS=${ALLOWED_HOSTS:-["*"]}
    DB_NAME=${DB_NAME:-""}
    DB_USER=${DB_USER:-""}
    DB_PASSWORD=${DB_PASSWORD:-""}
    DB_HOST=${DB_HOST:-""}
    DB_PORT=${DB_PORT:-5432}
    

    Note: the syntax above sets the default tag to 'debug', unless the TAG_OVERRIDE variable is present in the environment (this workflow sets the variable).

  • Finally, the environment variables you wish to substitute must be present as environment variables or secrets in your Github repository settings.

    • The environment name default is test.
    • It is possible to override which environment to use by setting workflow input environment.
    • Important: to ensure that secrets are passed to the workflow, you must add secrets: inherit (see examples below).

FAQ

Environment variables are not injecting

There may be an issue with your .env.example.

A template such as the example above is required to generate a .env file from .env.example.

In this example:

ALLOWED_HOSTS=${ALLOWED_HOSTS:-["*"]}

The variable ALLOWED_HOSTS will be substituted to:

# If ALLOWED_HOSTS is present in your environment
ALLOWED_HOSTS=["https://some.domain.org"]
# ["https://some.domain.org"] is the value in your Github environment

# If no variable is set in your environment, the default is used.
# This is the value after the :- above (in bash substitution syntax).
ALLOWED_HOSTS=["*"]

Hostnames are not resolving

For example, your app requires the database, but the database service name is not available.

An example in Django would look like:

django.db.utils.OperationalError: could not translate
host name "db" to address: Temporary failure in name resolution

This may be because the database has not initialised completely, prior to running the command.

To solve this, update your docker compose file to use depends_on, under your app service:

services:
  app:
    ...
    depends_on:
      db:
        condition: service_healthy

To facilitate this, it is good practice to add a healthcheck to containers. We should use a healthcheck that determines the database has started and is healthy.

If building a custom Dockerfile, you can add a HEATHCHECK directive.

But many will use a standard unmodified database image, so the heathcheck is added in docker compose:

services:
  db:
    ...
    healthcheck:
      test: pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-postgres}
      start_period: 5s
      interval: 10s
      timeout: 5s
      retries: 3

Coverage files are overwritten

  • If you run this workflow, then the mkdocs_build workflow directly afterwards, the coverage files may get overwritten.
  • To solve this, add the keep_extra_files variable to the mkdocs_build workflow to retain the coverage.html and coverage.svg badge.

Inputs

INPUT TYPE REQUIRED DEFAULT DESCRIPTION
build_context string false "." Root directory to start the
build from.
build_dockerfile string false "Dockerfile" Name of dockerfile, relative to
context dir.
build_img boolean false true If the image must be
built first, or false to
just pull.
build_target string false "ci" The target to built to
(default to ci stage).
cache_extra_imgs string false Space separated list of images
to cache on each run
(e.g. to avoid rate limiting).
cache_image boolean false true Cache the built image, for
the next run. Default true.
compose_command string false The command to run for
the container. Default to built-in
image command.
compose_file string false "docker-compose.yml" The docker compose file used
to run the test.
compose_service string true The docker compose service to
run the test against.
coverage boolean false false Generate a coverage HTML report
(requires coverage.py installed).
environment string false "test" The environment to use for
testing.
example_env_file_path string false ".env.example" Path to example dotenv file
to substitute variables for.
extra_build_args string false Space separated list of build
args to use for the
image.
image_name string true The image root name, without
tag. E.g. 'ghcr.io/[dollar]{{ github.repository }}'
pre_command string false A initialisation command to run
prior to the docker compose
command.
tag_override string false An override for the build
image tag. Must include tests
and have test software installed

Outputs

No outputs.

Secrets

No secrets.

Example Usage

Running during PR:

name: PR Test Backend

on:
  pull_request:
    branches:
      - main
      - staging
      - development
    # Workflow is triggered only if src/backend changes
    paths:
      - src/backend/**
  # Allow manual trigger (workflow_dispatch)
  workflow_dispatch:

jobs:
  pytest:
    uses: hotosm/gh-workflows/.github/workflows/test_compose.yml@main
    with:
      image_name: ghcr.io/${{ github.repository }}/backend
      build_context: src/backend
      extra_build_args: |
        APP_VERSION=${{ github.ref_name }}
        COMMIT_REF=${{ github.sha }}
      docker_compose_service: api
      docker_compose_command: wait-for-it fmtm-db:5432 --strict -- wait-for-it central:8383 --strict --timeout=30 -- pytest
      cache_extra_imgs: |
        "docker.io/postgis/postgis:${{ vars.POSTGIS_TAG }}"
        "docker.io/minio/minio:${{ vars.MINIO_TAG }}"
    secrets: inherit

Running prior to deployment:

name: Build and Deploy

on:
  # Push includes PR merge
  push:
    branches:
      - main
      - staging
      - development
    paths:
      # Workflow is triggered only if src changes
      - src/**
  # Allow manual trigger
  workflow_dispatch:

jobs:
  pytest:
    uses: hotosm/gh-workflows/.github/workflows/test_compose.yml@main
    with:
      image_name: ghcr.io/${{ github.repository }}/backend
      build_context: src/backend
      extra_build_args: |
        APP_VERSION=${{ github.ref_name }}
        COMMIT_REF=${{ github.sha }}
      docker_compose_service: api
      docker_compose_command: wait-for-it fmtm-db:5432 --strict -- wait-for-it central:8383 --strict --timeout=30 -- pytest
      tag_override: ci-${{ github.ref_name }}
    secrets: inherit
## Continue to deploy...

Last update: July 27, 2024