If you followed my previous post on Integration Testing With Docker-Compose you may have gone the next step and tried to implement this within your CI pipeline.
I use GitLab and usually set up my system the same as the gitlab.com system,
where jobs are run in Docker containers. When GitLab is running jobs in Docker containers
and those jobs run docker-compose
to instantiate Docker containers: it all gets “a bit Inception”.
So let’s look at how it all works.
First up, we need a container with both docker
and docker-compose
installed within it from which
we can run our GitLab job.
I did some searching, but didn’t find exactly what I needed, so I created one.
The Dockerfile
is pretty simple, using the base docker
image and adding docker-compose
like so:
FROM docker:18.06.1-ce
RUN apk add --no-cache py-pip && pip install --upgrade pip && pip install docker-compose
You can simply use the pre-built image from Docker Hub.
Then we’re going to need a GitLab CI YAML file that tells GitLab how to run our integration test.
The code below is a really simple CI file that uses the docker + docker-compose
image from above.
services:
- docker:dind
service-under-test:
image: service-under-test
integration-tests:
image: davedupplaw/docker-compose-ci:latest
stage: test
script:
- docker-compose build
- docker-compose up --abort-on-container-exit
after_script:
- docker-compose down --rmi 'all' || true
You can see we’re using the docker + docker-compose
image as the build image; that means our build will run within
this container. Then to run the tests, we build any containers from our docker-compose file (usually the
test image) and spin up the stack using ``–abort-on-container-exit` so that the stack is pulled down
when the test finishes.
Now, one thing to be aware of is how this manifests itself within the GitLab environment. GitLab runners that use Docker link to the underlying Docker machine on the host on which it sits. That means that the containers that are spun up from the compose stack as spun up as siblings to the build container.
This is the reason we also have an after_script
section which removes all the images that we pulled during
this build. If you’re reusing the same host machine for builds, then if service-under-test:latest
image already
exists, it will not force pull a new one. This means you can end up running integration tests against old
versions of your services, so we avoid that by removing all images after our test. It also ensures our
host machine will not fill up with images.
‘Where are my test reports?’, I hear you say.
They’re in the container in which your tests ran!
‘But that container shut down at the end of the tests’
Oh yes, it did. But you can still retrieve the reports by copying data out of the shutdown container.
However, you need to make sure that you do it in the after_script
so that if the test fails, the
tests reports will still get copied out. You also need to do it before you remove all the containers
and images.
services:
- docker:dind
service-under-test:
image: service-under-test
integration-tests:
image: davedupplaw/docker-compose-ci:latest
stage: test
script:
- docker-compose build
- docker-compose up --abort-on-container-exit
after_script:
- docker cp integration-tests:/path/to/repots ./reports
- docker-compose down --rmi 'all' || true
artifacts:
when: always
path:
- reports
So now the first line in the after_script
copies the reports from our test container into the
build container. This is then exposed to GitLab as an artifact via the artifacts
declaration.
We use the when: always
declaration to ensure that our artifacts are exposed even if the job fails.
For a real example, here’s a job we have to run integration tests against our UI, using Cypress.
ui-tests:
image: davedupplaw/docker-compose-ci:latest
stage: test
script:
- docker login -u $DOCKER_USER -p $DOCKER_PASSWORD <our-private-repo>
- docker-compose build
- docker-compose up --abort-on-container-exit
after_script:
- docker cp ui-test:/usr/src/myapp/cypress/screenshots ./cypress/screenshots
- docker cp ui-test:/usr/src/myapp/cypress/videos ./cypress/videos
- docker-compose down --rmi 'all' || true
artifacts:
expire_in: 1 week
when: always
paths:
- cypress/screenshots
- cypress/videos
I hope this was useful to you. If so, let me know in the comments below!
Comments: