How to setup Dockerized testing

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

This article is the second in a series about system testing:

  1. Dockerized testing vs end-to-end testing
  2. How to setup Dockerized testing
  3. Readable Java system tests with good old JUnit

You may want to read Dockerized testing vs end-to-end testing first.

How to setup Dockerized testing?

A prerequisite for running your tests against a dockerized environment is of course that the service you want to test is packaged as a Docker image. Also, all of the services which the System Under Test (SUT) depends on needs to be dockerized. If you don’t have that, your environment is not fully dockerized and you will suffer the consequences of having to test against external services.

If your SUT is packaged as a Docker image and has no dependencies on other services, you can just start it using “docker run” and run your tests against it. However, usually services have one or more dependencies to other services, such as its own database, a queue and perhaps a external RESTish service that returns nasty XML. You want each of these services in their own container. To spin up a bunch of docker containers, you can use docker-compose.

As an example, let’s say your service depends on a Postgres database, a RabbitMQ server and a external REST service which provides some data over XML.

The following is a docker-compose file which will spin up your entire system, including dependencies:

version: '2'
services:

  myapp-rabbit:
    container_name: myapp-rabbit
    image: rabbitmq:3.6-management
    ports:
      - "4369:4369"
      - "5671:5671"
      - "5672:5672"
      - "15672:15672"
      - "25672:25672"
    volumes:
      - ./myapp-rabbit/definitions.json:/etc/rabbitmq/definitions.json  # Example of how you can provision a dockerized RabbitMQ server

  myapp-database:
    container_name: myapp-database
    image: postgres:10
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: mydbuser
      POSTGRES_DB: mydb

  external-nasty-xml-stub:
    container_name: external-nasty-xml-stub
    image: nginx:1
    ports:
      - "8089:80"
    volumes:
      - ./external-nasty-xml-stub:/usr/share/nginx/html:ro  # This is where the stubs are inserted

  myapp:
    container_name: myapp
    image: kennethreitz/httpbin  # Using something external which can start, replace with your actual app and configure it to connect to the rabbit, the db and the external XML service.
    ports:
      - "8080:80"
    depends_on:
      - myapp-rabbit
      - myapp-database
      - external-nasty-xml-stub

You can find the docker-compose.yml file and the stubs and the example rabbitmq provisioning at https://github.com/betrcode/java-system-test-example/tree/master/docker-compose

Please note that this is just an example of how it could look. The services in these containers are not actually integrated, but in a real implementation they would be.

Configuration of dockerized services

Often you need to configure a dockerized service beyond the default settings. This is usually done in two ways:

  1. Environment variables
  2. Injecting entire configuration files using Docker volumes

The creator of a Docker image can have implemented support for certain settings and will use certain environment variables to override those settings. What environment variables are supported should be found in the documentation of the Docker image. For the postgres container, I have set POSTGRES_USER and POSTGRES_DB. Those are documented at https://hub.docker.com/_/postgres/.

In the RabbitMQ section, we see an example of using a Docker volume to inject a configuration file. The author of the RabbitMQ Docker image has implemented support for picking up a certain configuration file if it exists.

We simply inject a file using Docker volumes to a specific location and the container will load the file when starting up. This feature is not as well documented for this specific Docker image, but to learn what a Docker container will run when starting a container, to find these hidden gems, you can look at the ENTRYPOINT statement at the end of the Dockerfile. Often, this is a script rather than a single command. In the RabbitMQ Dockerfile:

ENTRYPOINT ["docker-entrypoint.sh"]

And when we look at the entrypoint script, we can see exactly what the container will run when starting up.


# if definitions file exists, then load it

# https://www.rabbitmq.com/management.html#load-definitions

managementDefinitionsFile='/etc/rabbitmq/definitions.json'

if [ -f "${managementDefinitionsFile}" ]; then

# see also https://github.com/docker-library/rabbitmq/pull/112#issuecomment-271485550

rabbitManagementConfig+=(

"{ load_definitions, \"$managementDefinitionsFile\" }"

)

fi

 

https://github.com/docker-library/rabbitmq/blob/da82eb0f68ed89a876fc44e915f20fc1e6e6bd8d/3.6/debian/docker-entrypoint.sh#L367

Reference: https://hub.docker.com/_/rabbitmq/

Using Docker to run test stubs

In the example above, we have a container named “external-nasty-xml-stub”. This is an example of how to use a docker container to stub an external service.

We start a plain nginx web server and use Docker volume to put a static file in the web root of nginx. Easy! In the example, we have “volumed in” an entire directory which is useful when you have several endpoints you want to stub. I find it convenient to put all files that I wish to “volume in” to my containers in a directory which maps to the container name, but this is just my convention. You can place them anywhere you wish.

You can use the same principle to implement a scripted fake instead of a static stub if that’s needed.

Let’s start the dockerized test setup!

Clone the repository: https://github.com/betrcode/java-system-test-example

Start a dockerized test environment by running:

docker-compose -f docker-compose/docker-compose.yml up -d

It will download all docker images and start your entire system, ready to be tested.

$ docker-compose -f docker-compose/docker-compose.yml up -d
Creating network "dockercompose_default" with the default driver
Creating myapp-database
Creating myapp-rabbit
Creating external-nasty-xml-stub
Creating myapp

You now have a Dockerized system up and running, ready to be tested. You can now point your system tests against the SUT and run the test suite.

If you are interested in how you can write Java system tests using plain old JUnit and RestAssured and execute them against a dockerized test environment (or any other test environment), check out the next post about Java system tests with good old Junit.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.