Last updated

Hanami and Multi-Database Testing with Travis

This is a re-post of my article over at Kabisa’s The Guild.

I’ve been busy rewriting Firefly for a while now using Hanami. Hanami is a fascinatingly fresh ruby web framework with a strong opinion on Clean Architecture. Me like!

Why test against different databases

Initially I developed Firefly to use Sqlite for database storage. However, Sqlite is not always the best option. Running Firefly on Heroku for instance would be impractical, since Heroku’s architecture assumes you use a real database, like Postgres.

Firefly being open source also means that different users will want to use a database that they’re already familiar with, or one that’s already running for other apps.

Supporting multiple databases

The big three relational databases I want to support are Sqlite, MySQL and PostgreSQL. Hanami uses Sequel. This means my code is already abstracted from specific database implementation. As long as Sequel supports it, so can Firefly.

The only problem I encountered was the fact that MySQL datetime fields do not store fractions of seconds, which messed with some tests. This was easily taken care of.

Travis

Travis is an awesome CI-as-a-Service provider. Open source projects can even use their service for free! <3

Whenever a new commit is made (on master or in Pull Requests), Travis will check out the code, do some setup specified in a .travis.yml file and report back the test status.

The thing is that, with multiple databases, we need to tell Travis to run multiple sub-builds for each database. We also need to tell Travis how to configure / setup Firefly to use each database properly.

Hanami and databases

Before setting up multiple databases, let’s check how Hanami configures a database connection.

1# lib/firefly.rb
2Hanami::Model.configure do
3  #  * SQL adapter
4  #    adapter type: :sql, uri: 'sqlite://db/firefly_development.sqlite3'
5  #    adapter type: :sql, uri: 'postgres://localhost/firefly_development'
6  #    adapter type: :sql, uri: 'mysql://localhost/firefly_development'
7  #
8  adapter type: :sql, uri: ENV['FIREFLY_DATABASE_URL']

So, the actual database URI is set using an environment variable. Hanami makes use of the dotenv gem, which will load a .env or .env.test file depending on which environment Hanami runs in. Somehow we’d need different .env.test files for each database configuration

For Sqlite:

1# .env.test.sqlite
2FIREFLY_DATABASE_URL="sqlite://db/firefly_development.sqlite3"

For MySQL:

1# .env.test.mysql
2FIREFLY_DATABASE_URL="mysql://root@localhost/firefly_test"

For Postgres:

1# .env.test.postgresql
2FIREFLY_DATABASE_URL="postgres://localhost/firefly_test"

There’s also the issue of dependencies. For instance, when using PostgreSQL, the pg gem should be included in Gemfile. If you’re running with Sqlite, you do not want that dependency there. Here I’d like to take the same approach and create multiple Gemfiles that each specify their own dependencies as needed.

For Sqlite:

1# gemfiles/Gemfile.sqlite
2gem 'sqlite3'

For MySQL:

1# gemfiles/Gemfile.mysql
2gem 'mysql'

For Postgres:

1# gemfiles/Gemfile.postgresql
2gem 'pg'

Tying it all together

What’s left to is tell Travis about the different database and put the right files in place at the right time.

First, I setup the environment variables for each database. This triggers Travis to run a build for each combination of variables:

1# .travis.yml
2env:
3  - DB=sqlite
4  - DB=mysql
5  - DB=postgresql

Travis will run the build three times, each time with a different DB value set.

The process of the Firefly build is like this:

1# .travis.yml
2install: bundle install --jobs=3 --retry=3 --without production
3
4script:
5  - 'HANAMI_ENV=test bundle exec hanami db create'
6  - 'HANAMI_ENV=test bundle exec hanami db migrate'
7  - 'bundle exec rake test'

What I want is hook into different places and setup the right .env and Gemfile for the specified database. As it turns out Travis provides before_install and before_script hooks. By making use of the specified DB environment variable, it really is just a matter of copying the right files into place.

 1# .travis.yml
 2before_install:
 3  - cp gemfiles/Gemfile.$DB Gemfile
 4
 5install: bundle install --jobs=3 --retry=3 --without production
 6
 7before_script:
 8  - cp .env.test.$DB .env.test
 9
10script:
11  - 'HANAMI_ENV=test bundle exec hanami db create'
12  - 'HANAMI_ENV=test bundle exec hanami db migrate'
13  - 'bundle exec rake test'

That’s all it takes to run your tests against multiple databases with Hanami!

Bonus: test against multiple rubies

Testing against multiple databases is cool, but it’s also very well possible that end-users will not be using the greatest and latest ruby version. Firefly currently support the latest 2.2.x and 2.3. versions of Ruby. Travis supports this out of the box:

1# .travis.yml
2rvm:
3  - 2.2.4
4  - 2.3.0

This, in combination with our database setup, will trigger six builds. Sqlite, MySQL and PostgreSQL builds on ruby-2.2.4 and on ruby-2.3.0.

Travis builds

I hope you liked this post. Happy coding and keep testing!