+++ date = "2016-04-15" title = "Hanami and Multi-Database Testing with Travis" tags = ["hanami", "travis", "testing"] description = "Hanami: good! Travis: good! Testing your code against multiple databases: priceless!" slug = "hanami-and-multi-database-testing-with-travis" +++ _This is a re-post of my article over at [Kabisa](https://kabisa.nl)'s [The Guild](https://www.theguild.nl/hanami-and-multi-database-testing-with-travis)._ I've been busy rewriting [Firefly](https://github.com/ariejan/firefly) for a while now using [Hanami](http://hanamirb.org). 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](https://heroku.com) 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](http://sequel.jeremyevans.net/). 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](https://travis-ci.org) 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. ``` ruby # lib/firefly.rb Hanami::Model.configure do # * SQL adapter # adapter type: :sql, uri: 'sqlite://db/firefly_development.sqlite3' # adapter type: :sql, uri: 'postgres://localhost/firefly_development' # adapter type: :sql, uri: 'mysql://localhost/firefly_development' # 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: ``` shell # .env.test.sqlite FIREFLY_DATABASE_URL="sqlite://db/firefly_development.sqlite3" ``` For MySQL: ``` shell # .env.test.mysql FIREFLY_DATABASE_URL="mysql://root@localhost/firefly_test" ``` For Postgres: ``` shell # .env.test.postgresql FIREFLY_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: ``` ruby # gemfiles/Gemfile.sqlite gem 'sqlite3' ``` For MySQL: ``` ruby # gemfiles/Gemfile.mysql gem 'mysql' ``` For Postgres: ``` ruby # gemfiles/Gemfile.postgresql gem '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: ``` yaml # .travis.yml env: - DB=sqlite - DB=mysql - 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: ``` yaml # .travis.yml install: bundle install --jobs=3 --retry=3 --without production script: - 'HANAMI_ENV=test bundle exec hanami db create' - 'HANAMI_ENV=test bundle exec hanami db migrate' - '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. ``` yaml # .travis.yml before_install: - cp gemfiles/Gemfile.$DB Gemfile install: bundle install --jobs=3 --retry=3 --without production before_script: - cp .env.test.$DB .env.test script: - 'HANAMI_ENV=test bundle exec hanami db create' - 'HANAMI_ENV=test bundle exec hanami db migrate' - '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: ``` yaml # .travis.yml rvm: - 2.2.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](/img/travis-firefly-builds.png) I hope you liked this post. Happy coding and keep testing!