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”

  1. Piers Cawley Says:

    In one of our work projects we have a table driven set of Specs which ends up doing something like:

    table.each do |row| 
      describe "Given row #{row.first}" do
        it "should do this" do
          ...
        end
        it "should do that" do
          ...
        end
        ...
      end
    end
    

    It's just one more reason why I like Rspec's approach.

  2. szeryf Says:

    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/

  3. Justin Says:

    This is just POSR... plain old standard ruby, ftw...

  4. Scott Taylor Says:

    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?

  5. Scott Taylor Says:

    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)

  6. Piers Cawley Says:

    Ooh, I hadn't thought of using it to iterate over classes.

  7. Shot Says:

    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.)

  8. remi Says:

    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.

  9. remi Says:

    d'oh ... 4 spaces ...

    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
      ['w00t',"what's up?",'chunky','elf fox ham'].each do |email| 
        Email.new( email ).should_new be_valid 
      end 
    end
    
  10. remi Says:

    one more time, without types ... sheesh ...

    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
      ['w00t',"what's up?",'chunky','elf fox ham'].each do |email| 
        Email.new( email ).should_not be_valid 
      end 
    end
    

    much better! i've incurred the wrath of smartypants.

  11. court3nay Says:

    Well that doesn't give you very good error messages, now, does it? You can't instantly tell which one failed.

  12. rick Says:

    That's because rspec's syntax doesn't let you customize messages the way assert_* methods do. You could do this though:

    it 'should check validity of email addresses' do
      %w( bob@company.com jim.smith@here.there.jp ).each do |email| 
        e = Email.new( email )
        fail "#{email} was invalid" unless e.valid?
      end
      ['w00t',"what's up?",'chunky','elf fox ham'].each do |email| 
        e = Email.new( email )
        fail "#{email} was valid" if e.valid?
      end 
    end
    

Sorry, comments are closed for this article.