Minitest Helper Includes and Rails Fixtures

Contuining my love affair with Minitest and fixtures, I wanted to dive in to something deeper this time. Switching tools takes time to get used to and managing passwords was one of the bigger headaches I encountered.

The Problem

I wanted to use Capybara for integration testing. The application uses the standard email/password combo to authenticate users via has_secure_password.

Because plain text passwords are not stored in the database, but rather a password_digest, I didn’t have a good way to fill in the login forms using Capybara to test against pages that require authentication.

The Solution

I started with a simple users.yml fixture that looked like this:

fred:
  first_name: Fred
  last_name: Flintstone
  email: fred@flintstone.com
  title: CEO
  company: flintstone
  admin: true

Bcrypt

As mentioned above, has_secure_password uses Bcrypt to encrypt the plain text passwords provided. The specific implementation to create the password digest is:

BCrypt::Password.create("password", cost: 4)

This method takes the plain text password (“password”) and encrypts it with what it calls the “cost”. It’s not terribly important for this article, but to save some speed, I’ve specified a cost of 4 — the minimum cost support by the Bcrypt algorithm. The default is 10, which will make your production applications safer. However, for testing, we don’t care.

The result of the method above looks like this:

irb(main):001:0> require "bcrypt"
=> true
irb(main):002:0> BCrypt::Password.create("password", cost: 4)
=> "$2a$04$gw09FM67MDnzduXmlK46BOsdVTtzWKaSIkAdmnF/sJSLgcQhJBAUe"

That output value is what’s being stored in our database when a user inputs and saves a password. From the application’s standpoint, we could print a value like this in to our fixtures, but what’s the fun in that?!?!

ERB in Fixtures

Since fixtures allow us to use ERB in them, we could provide the Bcrypt method above to produce the password digest like so:

fred:
  first_name: Fred
  last_name: Flintstone
  email: fred@flintstone.com
  password_digest: <%= BCrypt::Password.create("password", cost: 4) %>
  title: CEO
  company: flintstone
  admin: true

The downside is that the actual password (“password”) is here in plain text. To fill in our login form using Capybara, we don’t have a variable to access to the get the password — we literally have to type “password”. So this isn’t the most DRY thing in the world. While it would certainly work, I think we can do better…

Capybara

Let’s say I have a test that looks like this:

visit signin_path
fill_in "email", with: user.email
fill_in "password", with: "password"
click_on "Sign in"

Extract a Module

Our goal was to not sprinkle these plan text passwords all over the place. So let’s extract a module call TestPasswordHelper and put the plain text password in there:

require "bcrypt"

module TestPasswordHelper
  def default_password_digest
    BCrypt::Password.create(default_password, cost: 4)
  end

  def default_password
    "password"
  end
end

We’ll have our default password accessible via a method named…get this, default_password! The module also contains a method (default_password_digest) that will allow us to send the password digest to the fixture using the Bcrypt algorithm we explored above.

Now that we have a helper module ready all set up, we add the following to our test/test_helper.rb to make these methods accessible in our tests:

require "support/test_password_helper"

class ActiveSupport::TestCase
  include TestPasswordHelper
end

With these methods mixed in, we can update our Capybara test to use the default_password method:

visit signin_path
fill_in "email", with: user.email
fill_in "password", with: default_password
click_on "Sign in"

Helpers in Fixtures

Unfortunately, those helpers aren’t available in the fixtures.

ActiveRecord::FixtureSet is the class that gives our fixtures life. We can use Ruby to include functionality from our test helper, that will give us access to to the default_password_digest method, which reads our default_password so we don’t have to type it out.

The Rails API guide for fixtures states that helper methods should be added to ActiveRecord::FixtureSet.context_class.

So back in our test_helper.rb, we can mix in our test helpers methods like so:

ActiveRecord::FixtureSet.context_class.send :include, TestPasswordHelper

Now, back in our users.yml fixture, we can use the new default_password_digest method:

fred:
  first_name: Fred
  last_name: Flintstone
  email: fred@flintstone.com
  password_digest: <%= default_password_digest %>
  title: CEO
  company: flintstone
  admin: true

We can now run our tests and verify the fixtures properly insert the digest using the default password and the Capybara tests reference that same default password.

Now, if in the future we wanted use a different password for some reason, we’d only have one place to change it, and the rest of the system would follow along.

Summary

One of the things I’m constantly reminding myself during this process is whenever I have a problem, to step back and think about ways the Ruby language can help solve it rather than looking for some special sauce or gem to get me through the turmoil. Minitest is just Ruby — as most other gems are. Minitest generally provides enough utility to get us through the bigger use cases, but when it comes to special cases, it’s not there to hold our hand. That’s when we step up and make use of the language we’ve all come to know and love — Ruby!