In the first part of this series we looked at how using Сypress and choosing the right mocking strategy helped us write End-to-End tests that are both performant, reliable and easy to work with. To get the feeling of the snappiness check out this video that Cypress recorded for us during the test run. In this part, we will focus on another practical aspect of E2E testing - running tests on CI.
Use docker-compose
With E2E testing, we want to bring in as many parties (micro-services, APIs, transport) of the application as possible, so that we can ensure the best coverage and integration. Ideally, we should be testing a production clone, but that comes with a substantial overhead for performance - we don’t want to waste time and resources on deployment for every single build. What we want is to give a fast feedback to a developer if his commits introduced regressions or not. Here comes Docker. Docker-compose gives you this ability to declaratively bring all micro-services that your application needs together, run them on CI along with your tests.
Let’s assemble our application for testing in one nice docker-compose.yml file. For the demo we will describe a typical React/Node.js app consisting of 4 images here:
-
frontend - is the react app with a server that serves static files
-
API - is the Node.js API
-
MongoDB - persistence
-
Cypress - is our test runner that will open frontend image URL in the browser, but can also send requests to API to reset the state of the application
#docker-compose.yml version: '2' services: cypress: build: context: . dockerfile: Dockerfile.cypress links: - frontend - api command: /app/wait-for-it.sh frontend:3000 -t 60 -- npm run test frontend: environment: - NODE_ENV=integration build: context: . dockerfile: Dockerfile.frontend ports: - 3000:3000 expose: - 3000 links: - api command: /app/wait-for-it.sh api:4000 -t 60 -- npm run start api: environment: - NODE_ENV=integration image: 'noviopus/api-dev:latest' ports: - 4000:4000 expose: - 4000 links: - mongodb mongodb: image: mongo:3.2
Some notable things to note here.
Firstly, we are using the latest version of the API image here. The main idea is that API is developed and deployed in a backward-compatible way in regards to the frontend, so when a new version of API comes out, we know that all our deployed frontend will continue to work (within the specific environments). This allows us to evolve application without resorting to versioning of the builds.
Secondly, we are explicitly waiting for image’s dependencies to be ready to accept connections using this simple yet useful script so that we know that all services are ready before we run the first test.
Here is what Circle CI 2.0 configuration file look like with Docker-compose:
version: 2
jobs:
build:
docker:
#run all commands in this image:
- image: dziamid/ubuntu-docker-compose #ubuntu + docker + docker-compose
- checkout
- setup_remote_docker
- run:
#need to login so we can pull private repos from hub in the following runs
name: Login
command: docker login -u $DOCKERHUB_USER -e $DOCKERHUB_EMAIL -p $DOCKERHUB_PASSWORD
- run:
name: Build
command: docker-compose -p app build
- run:
name: Test
command: docker-compose -p app run cypress
- run:
name: Collect artifacts
command: |
docker cp app_cypress_run_1:/app/cypress/artifacts $(pwd)/cypress/artifacts
when: always #execute this run command on success or failure of previous run
- store_test_results:
#expose test results so you can see failing tests on the top of the page
path: cypress/artifacts
when: always
- store_artifacts:
#expose video and screenshots from cypress
path: cypress/artifacts
when: always
- run:
name: Deploy
command: |
# deployment is out of scope of this article
Running `docker-compose -p app -f bundle.yml run cypress` shows the glory of Docker-compose. This command will:
- start Cypress image and attach to its output
- find all dependencies of the Cypress image and start them in the background
- when the process in Cypress image will exit, it will gracefully terminate all the processes in the background
- after all the processes terminate, you can access
By exposing tests results you will see the summary of your tests on top of the page.
In the result, we have integrated E2E into the development workflow. Now we can evolve our micro-services and be confident that they can integrate with each other and the most critical application flows are working as expected.