Words and Code

One writer’s journey from words to code.

Tackling Those Tests, Part 3: Testing Made Easy With Gems

#technicaltuesdays, rails, testing

This blog post is part of a series on testing. Read Part 2 here.

If there’s anything that I’ve learned about testing while writing this three-part blog post series, it’s this: testing is freaking hard! It’s tough to figure out the correct syntax and conventions, particularly when you’re just starting out. Figuring out when to write a test is also a tricky business. And then sitting down to actually write the test is pretty difficult, too.

But there are ways to make it easier on yourself. And I should know, because I tried to write my tests the hard way, and I definitely do not recommend it. Here’s what I do recommend: GEMS! And lots of ‘em. There are a lot of Ruby gems out there, specifically designed to make your testing life easier.

So, to close off this epic series on testing in Rails, I’m going to share my two favorite testing gems: shoulda-matchers and database_cleaner. Once you know how to implement them, your testing life will forever be easier. Because, let’s be real – isn’t that the way testing should be?

You Shoulda Used Shoulda

Last week, we wrote some tests for a ReadingList object, which we tested using factories. To refresh your memory, every ReadingList has a title attribute, which is just a string value. An important test for this object is a title validation. But what would that look like? Probably something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require 'rails_helper'

describe ReadingList do
  context 'has a title' do

    let(:real_list) { create(:reading_list, title: "my list") }

    it 'has a title' do
      expect(reading_list.title).to eq("Books I Want To Read")
    end
  end

  context 'doesn't have a title' do
    let(:fake_list) { create(:reading_list) }

    it 'raises an error' do
      expect(fake_list.save!)to raise_error
    end
  end
end

Man, that’s a lot of work for a single line of Rails code, isn’t it? Wait a second – what if we could shorten this huge test into one line of code? Is is possible? Of course it is! And it’s easy too, because the shoulda gem does just that.

To actually use the gem, you just need to add it to the test group within your Gemfile:

1
2
3
4
group :test do
  gem 'rspec-rails'
  gem 'shoulda-matchers'
end

You also need to add require 'shoulda/matchers' to your rails_helper.rb file. Now we can rewrite our validation test as a single line:

1
2
3
4
5
require 'rails_helper'

describe ReadingList do
  it { should validate_presence_of(:title) }
end

Pretty awesome, right? We can do this for all of our validations now! But wait – there’s more! The shoulda gem takes care of more than just validations. It can also test for associations in ActiveRecord and actions in our controllers! You can even write super specific validations scoped to a particular attribute or object, like this:

1
it { should validate_uniqueness_of(:reading_list).scoped_to(:title, :user) }

YAHHHHSSS we are on a test-writing roll!

Clean Yo Database

Now that you can write tests like a pro, you probably have an overwhelming desire to write all the tests and create all the objects and build all the factories. And you totally should. But you should also make sure they don’t come back to haunt you.

What do I mean by that, exactly? Well, whenever you run your test suite, you want to be sure that you clean out your database and get rid of any old or repeat data. This is super important when you have a lot of tests. You want to be sure that your test database is getting cleaned after each test, so that the next test starts with an blank database.

Enter database_cleaner, a gem that does exactly what you think it does. Although we’re using this gem in conjunction with Rails and RSpec, it also comes in handy for testing with capybara and selenium. I was introduced to this gem by another developer who took one look at my failing test and asked me whether or not I had repeat data. I didn’t even realize that database inconsistencies were thing. But they are! And that’s why we’ll want to use the database_cleaner gem.

Inside of our /spec/rails_helper.rb file, there’s a large block that starts with the line RSpec.configure do |config|. That’s exactly where our database_cleaner gem code is going to live – inside of that block.

Inside of that block, we’ll stick in this little bit of code:

1
2
3
4
5
config.before(:suite) do
end

config.around(:each) do |example|
end

Our config.before(:suite) block is for all the things we want to do before our entire test suite runs. We’ll probably want to put in some code that will get rid of old data from previous test suites that we ran earlier.

The config.around(:each) block is for each individual test example that runs in our suite. This is where we would put certain lines of that only need to run for more specific tests (like a capybara test, for example).

But our tests are relatively simple in this example. So, inside of our before(:suite) block, we’ll add some instructions:

1
2
3
4
5
6
7
8
9
config.before(:suite) do
  begin
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
    DatabaseCleaner.start
  ensure
    DatabaseCleaner.clean
  end
end

The start and clean methods hook into the beginning and end of our test suite, ensuring that whatever lines are above actually get executed properly. But what do the two lines above do, exactly?

Well, the clean_with(:truncation) method clears out our test database completely, and the strategy = :transaction method sets the behavior of the database “cleaning strategy” to be a transaction. Avdi Grimm has a great blog post on what all of this means, and his explanation of transactions is easy to understand:

Transactions are very fast, and for all the tests where they do work—that is, any test where the entire test runs in the RSpec process—they are preferable.

Okay, cool. But what about our other block? We gotta put some stuff in there, too. But since we don’t have any specific instructions or strategies for any of our tests, we’ll just run each of our examples normally, inside of a single block:

1
2
3
4
5
config.around(:each) do |example|
  DatabaseCleaner.cleaning do
    example.run
  end
end

Nice! In just a handful of lines, we’ve saved ourselves hours of heartache trying to figure out why one of our tests isn’t passing. Well, at least you saved yourself hours of heartache. I already went through the pain of that while trying to debug my poorly-written tests! But one of us will never have to go through that, which I think is a cause for celebration!

tl;dr?

  • The shoulda-matchers gem helps you keep your code DRY and test for associations and validations. The database_cleaner gem keeps your database clean between tests and helps you avoid problems related to repeat data.
  • Both of these gems have well-written and maintained documentation. Read them both! (shoulda docs and database cleaner docs)
  • If you’re curious about the difference between transactions, truncations, and deletions, check out this post.