Close and Go BackBack to Viget

Reusing Contexts in Shoulda with Context Macros

Justin Marney
Justin Marney, Web Developer, May 26, 2009 4

Last month David wrote up a good explanation of how to create shoulda macros with blocks. Recently, I needed to reuse context behavior across a few different tests as well. Out of curiosity, I went in search of a more idiomatic solution and was able to find this ticket and its associated conversation. From the discussion surrounding that ticket, I learned that you can use the merge_block method to create nestable context macros.

class Test::Unit::TestCase
  def self.context_with_an_object(&block)
    context "With an object" do
      setup do
        @object = {:rock => 'on'}
      end

      should "do something fantastic" do
        assert @object[:rock], 'on'
      end

      merge_block(&block) if block_given?
    end
  end
end

You can use the context macro in one of your tests and it will accept a block as well as respect context nesting.

class ModelTest < Test::Unit::TestCase
  context "A great and wonderous test" do
    setup do
      @thing = {:creature => 'lagoon', :rock => 'on'}
    end

    context_with_an_object do
      should "do something specific" do
        assert_equal @thing[:rock], @object[:rock]
      end

      context "And a friend" do
        setup do
          @friend = {:rock => 'on'}
        end

        should "respect some nested context insanity" do
          assert_equal @friend[:rock], @thing[:rock]
        end
      end
    end
  end
end

You can also create your context macros such that they accept arguments.

class Test::Unit::TestCase
  def self.context_with_a_modified_object(modifier, &block)
    context "with a modified object" do
      setup do
        @object = {:mod => modifier}
      end

      merge_block(&block) if block_given?
    end
  end
end

Modifying the context behavior via an argument allows you to test a handful of edge cases without having to duplicate context code.

class ModelTest < Test::Unit::TestCase
  context_with_a_modified_object("dance!") do
    should "be modified" do
      assert_equal @object[:mod], "dance!"
    end
  end
end

I recommend using this technique sparingly and only to remove unnecessary duplication among your tests. It is possible to hide too much context behavior behind these macros and end up with tests that are difficult to understand and maintain. For contexts that aren't reused outside of a single file consider defining them at the top of the test file.

Morgan said on 05/27 at 06:29 PM

thanks.

James Bebbington said on 10/09 at 11:00 AM

Interesting post… I’m trying to take the same principle and use it as a log in helper in my functional tests.

Trouble is the user argument evaluates to nil inside the helper if I try and pass it a User instance. Passing in a string is fine so I guess there’s some strange scoping issue afoot. Any ideas?

James Bebbington said on 10/09 at 11:35 AM

Ah, fixed it. See linked gist in previous comment.

Justin Marney said on 10/12 at 09:46 AM

James,
Good to see you got this solved. In case you, or anyone else, was wondering what exactly is going on, take a look at this gist http://gist.github.com/208389. The short version is that variables defined in setup blocks are instance variables. Variables that are passed into context helpers are class variables (context helpers themselves are class methods). The gist explains that a little more thoroughly.

Thanks!
Justin

Name:

Email:

URL:

Not an infant or robot? Please prove it:
How many hours in a day?

Some HTML (strong, a, em) is allowed.

Notify me of follow-up comments?

We're the Developers

at Viget Labs. We write about web development trends, tips, best practices, industry events, and our projects — all with an emphasis on Ruby on Rails.

Recent Comments

@bill: Thanks for the pointer! There’ve been a number of great articles popping up lately - it might be nice if there were a central place to see them all (heck, maybe I’ll do that if I can find the time over the weekend).

Contact Us

Have any questions, comments, ideas, or secrets to share? Let us know.


How many minutes in an hour?

Sorry, you need to have Javascript enabled to use this form. (Don't blame us, blame the spammers!) If you'd like to contact us, please visit our Contact page.