featureenvy

The blog of a thinkerer.
By @featureenvy

All rights reserved.

rspec-given Shows How RSpec Can Be Extended

Just recently Jim Weirich released rspec-given 2.0, an extension to the RSpec DSL. It tries to add given, when, then semantics to RSpec. Why? Because given, when, then state intent. So everything in a given will be something that you expect to be present. when is the part that should actually be tested and then states what should happen. This makes it easier to see what is just a test helper, which code is actually under test and what should happen to the different moving parts. Go read the readme in the rspec-given repository, Jim does a way more thorough job at explaining the semantics. Or if you know Cucumber, you can just continue! For the rest, I will be waiting, right here. Back already? Good! Let's continue!

A Primer on RSpec Given

So, if we want to write a Math library that uses a very strange notation, a spec could look like this:
describe "Math" do
	describe "#+" do
		it "adds two values" do
			let(:value_one) { 2 }
			let(:value_two) { 3 }

			Math.+ value_one, value_two

			Math.result.should == 5
		end
	end
end
I know, I know, it's a very contrived example, sorry... :( But what are we testing now? Math.+ or Math.result? With rspec-given, we could specify intent a lot clearer:
describe "Math" do
	describe "#+" do
		context "adds two values" do
			Given(:value_one) {2}
			Given(:value_two) {3}

			When { Math.+ value_one, value_two }

			Then { Math.result.should == 5 }
		end
	end
end
So we have two preconditions (setting value_one and value_two), which are just there to help the test. Then we have the code we actually want to test (Math.+). And we have a post condition, that needs to be true at the end. But in the second example it is clear that we don't test Math.result, but Math.+! Also notice that then replaces the need for an it block. I guess we all can agree that the second example is way more specific which code is there for what purpose. Which I like. But that got me to ask one question: How do you extend the DSL? Let's see:

Extending DSLs

First things first (because it wouldn't make sense to mention the first thing at the end I guess? That saying has never made sense to me, sorry...), I'm looking at rspec-given-2.0.0.beta3. This way, Jim can't ninja change things in there and then tell me I'm totally wrong. Second things second (that doesn't exist, does it?): Let's have a look at the first file: rspec/given.rb. If we ignore RSpec 1 (someones still using that?), we include different files. One of the interesting files is configure.rb. Here the important lines (actually, all lines in this file except the includes):
RSpec.configure do |c|
  c.extend(RSpec::Given::ClassExtensions)
  c.include(RSpec::Given::InstanceExtensions)
  c.include(RSpec::Given::HaveFailed)
end
(On Github) He uses RSpec.configure to extend and include a few extensions. If we take a look at RSpecs rubydocs, we can find RSpec.configure. Even more important, the Configuration object has two methods: extend and include! Now that's handy, because that's exactly what we used! These two are really similar. #extend is used to add methods to example groups (but not the examples itself). It is added to the describe block, but not the it block. Now guess what #include does. Right! It adds methods to the example itself, but not to the example group! Now we know how we can add custom stuff to RSpec examples and example groups! Which is exactly what rspec-given does! The class and instance extensions can be seen in extensions.rb.

That's How You Extend a DSL

RSpec nicely enough provided everything we need to extend it's syntax with our own methods. Of course this isn't just handy for rspec-given. Everyone can extend RSpec with its own syntax, which might come in handy once in a while. I suggest you have a look at the RSpec Configuration object, because it can actually do a lot. Did you know that it can show you the 10 slowest examples, automatically? Or that RSpec has before, after, and around hooks? So not only have we learned what rspec-given is, how it works, but also how we can extend RSpec ourselves! Thanks Jim! If you liked this post, you should consider hiring me freelance, part-time or full-time!