There are some big advantages to running tests in a container within a CI pipeline, including:
- Dependencies are within container - no need to install on the build agent
- Local development envirionment same as build environment
- Easier to debug build issues by running the container locally
The trick with running tests within a container is getting the test results out of the container and publishing them to the CI tool (in this case, Azure Devops).
Starting with a small JS app with Jest tests, with the following test command defined in the package.json
to output the tests as an XML file:
1 | "scripts": { |
We then create a simple docker file that will build the app and run tests. We define a label so we can pick the correct image later, and also output the status code of the yarn test
command so we can verify the tests ran properly later on (credit to this StackOverflow question for this solution).
1 | FROM node:15-buster |
We can then build the container in our pipeline:
1 | - task: Docker@2 |
Once built, we need to retrieve the test file from the image. To do this, we need to run the container and copy the file out. The container doesn’t have to be running wheh this happens - we can retrieve it from a container that is no longer running. We use the label we set in the Dockerfile to pick the correct container:
1 | - displayName: Copy test results from container |
We then publish the file to Azure Devops, so we can view the testing results in the UI. This step can also fail the build if there are failed tests.
1 | - task: PublishTestResult@2 |
Finally, we must check the output of the yarn test
command that ran in the container. This is because in some scenarios when the tests don’t run correctly (for example, an error starting any of the tests), the output file can be empty and not include any failures.
1 | - displayName: Verify tests passed |
And that’s it! For brevity I’ve not included some important aspects of a production-ready pipeline. Be sure to look into multi-stage docker builds to reduce image size and attack surface - they will still work with the approach above - the label will allow you to find the correct image.