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 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 2 3 4 5 6 7 8 # 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
depending on which environment Hanami runs in. Somehow we’d need different
.env.test files for each database configuration
1 2 # .env.test.sqlite FIREFLY_DATABASE_URL="sqlite://db/firefly_development.sqlite3"
1 2 # .env.test.mysql FIREFLY_DATABASE_URL="mysql://root@localhost/firefly_test"
1 2 # .env.test.postgresql FIREFLY_DATABASE_URL="postgres://localhost/firefly_test"
There’s also the issue of dependencies. For instance, when using PostgreSQL,
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.
1 2 # gemfiles/Gemfile.sqlite gem 'sqlite3'
1 2 # gemfiles/Gemfile.mysql gem 'mysql'
1 2 # 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:
1 2 3 4 5 # .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:
1 2 3 4 5 6 7 # .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
for the specified database. As it turns out Travis provides
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 2 3 4 5 6 7 8 9 10 11 12 13 # .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:
1 2 3 4 # .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.
I hope you liked this post. Happy coding and keep testing!