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:
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:
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:
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:
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:
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:
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:
With these methods mixed in, we can update our Capybara test to use the default_password
method:
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:
Now, back in our users.yml
fixture, we can use the new default_password_digest
method:
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!