I recently read a post from Carbon Five about RSpec best practices. The most delightful thing about it was reading it after I’d been writing a spec at work and noticing that how I was doing it was close to what was being described. It was a little bit of validation, a pat on the back for all of the reading, practicing, and thinking about BDD that I’d done to that point. But then Carbon Five asks “so what else?”

I recalled some of my first experiences with RSpec. For one job interview with a company that was using Rails, I was asked to bring in a small Rails app, a toy problem that they previously set for me to test my skills. I worked really hard on that application, struggling with every feature because at the time I had done relatively little with Ruby and Rails. I was specifically asked to make sure that this app was well tested. I had been doing some reading about TDD around that time, which led me to BDD, and in the context of Ruby that led me to RSpec. So I used RSpec and Spec::Rails, though I wasn’t entirely familiar with them.

During that interview, the developers sitting in asked me to add a new feature to my app. I remember that I needed to check that a particular value went up by 1 when I called a method. So I stored the old value in a local variable, invoked the method, and then checked that the new value was what I expected it to be.

“That’s good, but it can be better.”

From there, I was walked through something that at the time I couldn’t fully comprehend. I was asked to create a lambda with a block invoking the method, append .should to that, a space, and then change, with another block that would produce the value I wanted to check, and then .by(1). The three-line example I’d originally written became a single (albeit long and, for me at the time, hard to read) line. I was assured that it did precisely the same thing and they asked me to run it. Sure enough, it passed. That was my introduction to a whole side of RSpec I’d never learned about before: matchers.

I always wrote should == or should_not ==. I just thought of it as the particular way that RSpec did assertions, just a small part of the entire DSL. But he’s much more powerful than he actually looks. When you call should on something you pass an argument. That argument is a matcher. But what is a matcher? A matcher takes an object, evaluates some sort of predicate on it, and says “yes, that matches” or “no, it doesn’t”. It can also define a friendly message for failure (that’s what you see when an expectation fails while running your specs) and a description of what that matcher asserts, which appears in the documentation output format (“specdoc” if you’re still using RSpec 1.x) if you called #it with just a block and no string.

So let’s look at something similar to what I was walked through during that interview:

describe Foo do
  before(:each) do
    @foo = Foo.new
  end
 
  describe ".harbl" do
    it "should raise 'bar' by 1" do
      lambda { @foo.harbl }.should change { @foo.bar }.by(1)
    end
  end
 
end

Now, say we’ve implemented everything except the part where calling #harbl on a Foo increases the value by 1. When we run this, we’ll see a message saying something to the effect of “{ @foo.bar } should have been changed by 1, but was changed by 0.” Here, we see the first benefit using a special matcher instead of a raw assertion: the failure message actually reflects the behaviour we were expecting to see! Instead of seeing “1 doesn’t equal 2” which tells us little about what we were actually testing, we see “@foo.bar should have changed, but it didn’t.”

So how does this actually work? We’ll take this from the inside and work our way out. The “magic” here is change. It’s a method, but what does it return? It returns a fresh instance of Spec::Matchers::Change (in fact, the definition of the method is right in that file!). The block that you pass it tells the matcher what it’s supposed to evaluate to get the value before and after. We call #by on that which is just a little decorator. It tells the matcher what kind of change it’s looking for. We’re telling the matcher “run that block I gave you before and after running the code I plan to give you later. The value you get from that block after running the code should be 1 greater than it was when you ran it before the code.”

So now the matcher knows how to get the value, and it knows what we expect it to change by. It needs one last ingredient: a subject. What piece of code does the matcher need to run so it can observe the change in the value? That’s where #should comes in. He’s pretty simple: call him on something and he’ll take that something, pass it over to the matcher that he’s given as an argument, and say “tell me if this matches or not.” And just like that we’ve turned the matcher that we’ve built up into an expectation on the behaviour of our code.

That’s pretty nifty all on it’s own, but here’s where it becomes truly beautiful: RSpec comes with matchers for just about every common predicate you can think of. Not only does it come with all of these matchers, but it comes with methods that dynamically generate matchers! Wait, what?

describe Orange do
  before(:each) do
    @orange = Orange.new
  end
 
  it "should be tangy" do
    @orange.should be_tangy
  end
end

While the guys who made RSpec included a lot of matchers, I’m pretty sure a “be tangy” matcher is not one of them. And yet this is a perfectly valid RSpec example, without any special extensions or monkey patching. How? It turns out that RSpec’s “be” matcher is a lot smarter than it looks. If you connect it to something with an underscore, it will send that something with a ? on the end as a message to the subject and determine pass or fail based on the response.

Here’s the code we would write to make that spec pass:

class Orange
  def tangy?
    true
  end
end

And there you have it. We have a tangy orange, and RSpec lets us specify that it should be tangy. There is so much of this kind of awesomeness in RSpec that I can’t fit it all into this post. You really should explore RSpec matchers for yourself. If you somehow find a predicate that RSpec cannot express with one of its matchers, you can easily create your own. Just follow the model of RSpec’s other matcher classes; define a few necessary methods and some decorators if you need them and you’re done. If you’re using RSpec 2, I hear they’ve even created a small DSL to make custom matchers even easier to generate. Spiffy!

So if I could give only one piece of advice about RSpec best practices, it would be this: if your expectation looks messy, RSpec probably has a matcher that can make it cleaner.

Use the matchers, Luke.