rspec-given Shows How RSpec Can Be Extended
17 Sep 2012
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!