Handy dynamic rspec tip
Courtenay : January 21st, 2008
Say you have an email validation regex that you want to quickly spec against a bunch of possible addresses, good and bad.
Did you know rspec allows you to loop over arrays, dynamically creating describes and “it”s at will? I solved this tonight like so, with the help of cristi and some code of rick:
[ "joe@foo.bar", "m@test.co.uk", "23@233.com", "joe.fake@gmail.com", "test+me@yeah-you-99.com"].each do |add|
it "validates '#{add}' as a valid address" do
@mail_account.email_address = add
@mail_account.should be_valid
end
end
[ "joe@foo", "joe", "joe@xa."].each do |bad|
it "rejects obviously bad address '#{bad}'" do
@mail_account.email_address = bad
@mail_account.should_not be_valid
end
end
Dynamically creating each “it” block with a custom message so you can see the exact failure.
12 Responses to “Handy dynamic rspec tip”
Sorry, comments are closed for this article.
January 21st, 2008 at 02:14 AM
In one of our work projects we have a table driven set of Specs which ends up doing something like:
It's just one more reason why I like Rspec's approach.
January 21st, 2008 at 02:23 AM
This is not limited to rspec. I wrote about using the same approach for defining tests in traditional Test::Unit a while ago: http://szeryf.wordpress.com/2007/09/26/looping-in-a-no-mans-land/
January 21st, 2008 at 08:30 AM
This is just POSR... plain old standard ruby, ftw...
January 22nd, 2008 at 07:07 AM
Yep - it's the power of anonymous functions.
I do the same sort of trick for ActiveRecord getters/setters
columns = ["user_id", "foo", "bar"] columns.each do |col| it "should have the getter #{col}" do record.should respond_to(col) end
it "should have the setter #{col}=" do record.should respond_to("#{col}=" end end
You may say this isn't very behaviour driven (and I agree) - but there may be getters and setters which you'll depend on in views (such as createdon, updatedon), which you'll likely never spec out otherwise.
The technique you mention, though, raises the question: how dynamic should our specs be? I think your example maintains readable by anyone who knows ruby - and I think that's where dynamism should end.
Thoughts?
January 22nd, 2008 at 07:09 AM
Ah - re. Piers' comment:
This technique can also be very helpful with STI, and is a stand in for more powerful shared specs (since they don't yet exist)
January 22nd, 2008 at 01:09 PM
Ooh, I hadn't thought of using it to iterate over classes.
January 23rd, 2008 at 08:31 AM
A quick nitpick: what’s wrong with the joe@foo address? It’s perfectly valid, just as " spaces! @s! \"escaped quotes!\" "@łódź.pl is. :)
(See the thread around http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-talk/173923?173909-178505 for further reference.)
January 24th, 2008 at 08:01 PM
I very often do the opposite of what many of you are mentioning. I would say ... if you're not looking through a LOT of things that're making your specs for you ... okay. But I like to keep my specdoc looking ... not spammy.
I typically do something like:
it 'should check validity of email addresses' do %w( bob@company.com jim.smith@here.there.jp ).each do |email| Email.new( email ).should be_valid end end
Or, these sorts of things ("let's throw lots of data at this and see if it works") are also good candidates for using RSpec's User Stories.
If you want your specdoc to have all of the different examples (each as its own specification), I would use your way of doing it - but these things often fall under 1 specification, in my head.
January 24th, 2008 at 08:04 PM
d'oh ... 4 spaces ...
January 24th, 2008 at 08:06 PM
one more time, without types ... sheesh ...
much better! i've incurred the wrath of smartypants.
January 24th, 2008 at 10:19 PM
Well that doesn't give you very good error messages, now, does it? You can't instantly tell which one failed.
February 9th, 2008 at 09:43 PM
That's because rspec's syntax doesn't let you customize messages the way assert_* methods do. You could do this though: