Dockerising Rails App Tests with Databases — For CI
By: Leah Eramo
Tags: Postgres, Test, Rails, Docker, Development

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 in several 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 than 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 groundbreaking, 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 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 localhost.

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.)