Our hypothetical Rails app: the Foo Club
Let's imagine that Foo Club wants you to build an application to display events. These events are of two types—public and private:
- Public events are visible to anyone visiting our application.
Say someone prospecting, to determine if the Club is a right fit. - Private events are only visible to registered, authenticated members.
Also, only the admins should be able to edit, delete etc. events.
Authentication: minimal, yet powerful
Our app uses a standard email and password combination to authenticate the Club's members. It does this by leveraging has_secure_password
, which adds methods to set and authenticate against a BCrypt
password. This way, no plain text passwords are persisted in the database, but a password_digest
is what gets stored instead.
Fixtures are but YAML files, just better
As any fan of fixtures, we avoid spreading definitions of different types of members all across our tests. We isolate them in one place instead, in YAML files.
Say we want to model two different kinds of users, and we pick Jax as a member of the Foo Club and Tig, who's not convinced yet; he's still looking around:
# test/fixtures/users.yml
member:
name: Jackson Teller
email: jax@example.com
visitor:
name: Alexander Trager
email: tig@example.com
As a member, Jax needs a password, but as we mentioned earlier, these are never stored in plain text. Instead, we need to generate the password_digest
when the record is created, based on the unencrypted password. For a good example, let's see how Rails does it:
# …
self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost)
# …
One advantage of fixture files over classical YAML files is that we can inject dynamic values using ERB. We can now easily inherit the logic we learnt from Rails in our fixture. Maybe the only unknown left is that cost
: what values can it take?
When we're running our tests, and don't care that much about encryption strength; we want them to run fast. So how low can dial this down? Looking at bcrypt
, the minimum cost supported by the algorithm is 4. Then easy:
# test/fixtures/users.yml
member:
name: Jackson Teller
email: jax@example.com
password_digest: <%= BCrypt::Password.create('password', cost: 4) %>
# …
Keep it short and simple
Getting to know the big picture a little better will allow us to reduce our code one step further. This test confirms that in the test environment the minimum cost is used when computing the secure password. Armed with this knowledge, we can make our fixture file more succinct:
# test/fixtures/users.yml
member:
name: Jackson Teller
email: jax@example.com
password_digest: <%= BCrypt::Password.create('password') %>
# …
Next steps
Now it's easy to go ahead writing our controller or integration tests, adding a helper method that automatically logs the member in, add different kinds of users (maybe an admin?) and check if they can delete an event while regular members, or simple visitors cannot. You name it!
Cover image credit: https://500px.com/photo/63010225/love-locks-by-karin-pezel