Dockerising Rails App Tests with Databases – For CI

This post is written for Ruby developers who use Docker with a CI tool (such as Bamboo, Buildkite etc.) who want to integrate tests that require a database into their CI pipelines but also wish to run their tests locally without Docker.

Rails CRUD apps can be tested a number of different ways… Most CRUD actions don’t require unit tests per se, given that testing a very simple CRUD method would essentially be the same as testing whether or not ActiveRecord works, which is not very DRY. An argument can be given that you’re testing your implementation but that is neither here nor there. I want to focus more on how to test CRUD functions when using CI.

I’ve recently discovered a few reasons to unit test my controller methods, as they were a little more complicated that a simple CRUD controller method. My way to test these methods was to check that the database contained expected records/number of records after the fact.

This isn’t exactly ground breaking, if you use a Rails scaffold you’ll notice that some of the pre-written tests do the same thing, however the requirement for a test database can lead to a few curly issues when it comes to CI (Continuous Integration.)

The need for a test database when running tests can be easily fixed by using Docker-compose to spin up a database container and linking it to your test container. So my docker-compose.yml file will look like this:

  postgresql:
    image: postgres:latest

  test:
    image: ruby:2.5
    volumes:
      - .:/app
      - ruby2.5-bundle-cache:/usr/local/bundle
      - bundle-audit-cache:/root/.local/share
    working_dir: /app
    environment:
      BUNDLE_GEMFILE: /app/Gemfile
    ports:
      - 3000:3000
    environment:
      RAILS_ENV: test
    links:
      - postgresql

And my database.yml test database like this:

test:
  <<: *default
  database: lendr_api_test
  host: postgres
  user: postgres

Which is pretty run of the mill, a simple Google search will provide you with that solution…

Here’s where it got tricky for me… I wanted to have my Docker cake and eat it too… or to put in a way that actually makes sense, I wanted my CI pipeline (in this particular case Buildkite, for those playing the home game,) to use the Dockerised database but on my local machine, just run it locally, to save time.

That’s where the Googling fell short, and I had to rack (pun unintended) my brain. Just before I was ready to give up and accept a life of running Docker-compose test locally, like a pleb, something caught my eye where I didn’t expect it would. This particular line of code (line 7) helped me to strike gold.

# PostgreSQL. Versions 9.1 and up are supported.
#

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

You can embed Ruby code in your database.yml file in Rails!!!!!!

Now I can set my database host to vary depending on the existence of an environment variable, which I only plan to set in my docker container!

Turning my Docker compose file into:

  postgresql:
    image: postgres:latest

  test:
    image: ruby:2.5
    volumes:
      - .:/app
      - ruby2.5-bundle-cache:/usr/local/bundle
      - bundle-audit-cache:/root/.local/share
    working_dir: /app
    environment:
      BUNDLE_GEMFILE: /app/Gemfile
      DOCKER_HOST: postgresql
    ports:
      - 3000:3000
    environment:
      RAILS_ENV: test
    links:
      - postgresql

And my database.yml file into the following:

test:
  <<: *default
  database: lendr_api_test
  host: <%= ENV.fetch("DOCKER_HOST") { 'localhost' } %>
  user: postgres

Notice on line 4 how the host will default to localhost if the “DOCKER_HOST” environment variable is not found. Meaning that because I haven’t set the environment variable locally the host will default to local host.

And that is all there is to it… 2 lines of code so you can have your cake and eat it too.

And presto!

when you run the following bash script as part of your CI pipeline:

docker-compose run --rm --service-ports test ./auto/dev-db-refresh

docker-compose run --rm --service-ports test bin/rails test

Your CI pipeline will be able to run the database tests through Docker-compose… (Note that the “./auto/dev-db-refresh” command is just a script I wrote to drop the db, create the db, and run migrations.)

Leave a Reply

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