Gems I've Known and Loved #1: Expectations


Jun 08, 2008

Recently, I realized that something was wrong with all of the conventional testing and specing libraries.

One of the things I loved about RSpec, when I read thin's specs, for the first time, was how easy it was to read the assertion code:

some_var.should == 5

But, when I tried using RSpec on a project, it continually frustrated me that I had to describe each test twice (by naming it, and writing pseudo-english code):

it "should augment the count by one" do
  some_var += 1
  some_var.should == 5
end

So, I went back to the familiar Shoulda. But, then, a couple of weeks ago, I came to a realization: Shoulda has exactly the same problem — it's just hidden under the awesome set of macros. Hell, even test/unit has this problem. Test names are comments. And, frankly, many, if not most of the tests that I write (like the example above) just don't need commenting — they're simple enough that a comment is unnecessary verbosity. A comment may even be distracting the reader from actual test code. Then, a couple of days later, Jay Fields blogged about exactly that. That's what made me take a look at his testing framework: expectations.

Naming Tests with Code

When I was working with RSpec, I started to wish that I could do something like this, to avoid the naming penalty:

before do
  some_var += 1
end

some_var.should == 5
some_var.should be_an_instance_of?(Fixnum)

Shoulda can do something like that, but it doesn't work in all situations, because it gets called at the class scope, so you have to go guessing about what the name of the instance variable will be. Besides, since we're sticking to one assertion per test, couldn't the assertion be the name of the test?

some_var.should == 5 do
  some_var += 1
end

Oops, we just derived the syntax for expectations.

Assertions are King

Except that expectations takes it a few steps further. It provides a ton of niceties for describing your tests in sensible language, and great mocking facilities. Our last example would look like this:

expect 5 do
  some_var  = 4
  some_var += 1
end

If we wanted to test for Fixnum, like in our second assertion, above, there's a nice shorthand for that.

expect Fixnum do
  some_var  = 4
  some_var += 1
end

As I write this, I can hear somebody out there screaming: "That's not very DRY!!". Even though I prefer the duplication in this particular example for readability reasons, let's DRY it up, just for fun.

setup = lambda do 
  some_var  = 4
  some_var += 1
end

expect(Fixnum, &setup)
expect(5, &setup)

Looks a lot like what I was wishing for with RSpec, doesn't it?

Next, let's look at a mocking example. One of the things that makes mocking a little bit weird with conventional testing and specing frameworks is that assertions come last, but mocks come first. So, when you're trying to follow the one assertion per test pattern, you end up with two different flows: setup, assert, for assertions, and: mock, setup, with mocks. Since assertions always come first with expectations, there's only one possible flow, making tests more readable.

expect mock.to.receive(:some_method).with(1) do |my_mock|
  my_mock.some_method(1)
end

Finally, spec junkies can even write expectations using BDD-style language.

expect klass.new.to.have.finished do |process|
  process.finished = true
end

Caveats

Expectations is pretty new, so I haven't yet seen any niceties for testing rails. It would be great to be able to do something like:

expect controller.to_render('index') do |c|
  get :index
end

As a big fan of Shoulda, I'd love to see some of the same types of macros for expectations, too (not in the core framework, but as an add-on).

Get It

$ sudo gem install expectations

Check out the RDoc for more examples, and a full set of documentation.