[rspec-users] nested blocks

Chris Hoffman chris.c.hoffman at gmail.com
Wed May 16 10:27:16 EDT 2007


Okay, I killed two birds with one stone, I hope.  I saw what you meant
by my overly clever approach not making much sense to someone who is
trying to read the code.  I have made a custom matcher instead.  I
would appreciate any more comments you may have.

I have made every controller spec define its necessary collections,
which is what feeds into the matcher.  I wouldn't mind a more elegant
approach, such as some keyword in the model association definition
that declares it to be assigned by the model.  For example:

class Foo < AR::Base
  belongs_to :bar, :assignable
end

Anyway, here is what I have so far for the custom matcher.

module CustomCRUDMatchers
  class BePopulatedWith
    def initialize(collections_to_populate=nil)
      @collections_to_populate = [*collections_to_populate]
    end

    def matches?(assigns)
      @assigns = assigns #not sure why I need this to get inside the
following block
      @collections_to_populate.each do |collection|
        unless @assigns[collection].eql? "the #{collection}"
          @failed_collection = collection
          return false
        end
      end
    end

    def failure_message
      "expected #{@failed_collection} to be populated with 'the
#{@failed_collection}', got #{@assigns[@failed_collection].inspect}
instead"
    end

    def negative_failure_message
      "expected #{@failed_collection} not to be populated, got
#{@assigns[@failed_collection].inspect} instead"
    end
  end

  def be_populated_with(collections)
    BePopulatedWith.new(collections)
  end
end

describe "Foo#new" do
  include CustomCRUDMatchers

  before(:each) do
    @action = "new"
    @collections_to_populate = :bars
  end

  it "should render the '#@action' template" do
    get @action
    response.should render_template(@action)
  end

  it "should populate collections relevant to the form" do
    get @action
    assigns.should be_populated_with(@collections_to_populate)
  end
end

On 5/15/07, Chris Hoffman <chris.c.hoffman at gmail.com> wrote:
> Sorry about being incomplete; here is the whole error message:
>
> NoMethodError in 'PagesController#new should populate any necessary
> collections '
> undefined method `get' for #<Spec::DSL::EvalModule:0xb6f2397c>
> ./spec/controllers/../controllers/crud_spec_helper.rb:88:
> ./spec/controllers/../controllers/crud_spec_helper.rb:17:in
> `it_should_populate_collections'
> script/spec:4:
>
> On 5/15/07, David Chelimsky <dchelimsky at gmail.com> wrote:
> > On 5/15/07, Chris Hoffman <chris.c.hoffman at gmail.com> wrote:
> > > Hello,
> > >
> > > Does anyone have an explanation for why the following code results in
> > > the error that follows?  I also would value any criticisms of my
> > > approach (i.e., defining it_should_populate_collections).  Thanks
> > >
> > > -Chris
> > >
> > > #Code
> > >
> > > def it_should_populate_collections(condition=nil)
> > >   it "should populate any necessary collections #{condition}" do
> > >     [*@collections_to_populate].each do |collection|
> > >       collection_model = collection.to_s.classify.constantize
> > >       collection_model.should_receive(:find).with(:all).and_return("the
> > > #{collection}")
> > >     end
> > >     yield
> > >     [*@collections_to_populate].each do |collection|
> > >       assigns[collection].should == "the #{collection}"
> > >     end
> > >   end
> > > end
> > >
> > > describe "Foo" do
> > >   before(:all) do
> > >     @collections_to_populate = :bars
> > >   end
> > >
> > >   before(:each) do
> > >     @obj = Foo.new
> > >   end
> > >
> > >   it_should_populate_collections "on a save failure" do
> > >     @obj.should_receive(:save)
> > >     post @action
> > >   end
> > > end
> > >
> > > #Error
> > >
> > > undefined method `post' for #<Spec::DSL::EvalModule:0xb6e7b2e0>
> >
> > post is only available in controller specs, identified by living in
> > spec/controllers or by saying:
> >
> >   describe "Foo", :behaviour_type => :controller do
> >
> > My guess is that you're doing this in spec/models, but that's just a
> > guess (please include complete error messages instead of just one line
> > when you post questions like this).
> >
> > As for the approach:
> >
> > 1. I'd recommend only using before(:all) for expensive operations like
> > initializing databases. In this example, there is no benefit to using
> > before(:all). Not even a runtime benefit, because any instance vars
> > that you set up get copied around which is no less expensive than
> > simply re-creating them before(:each) example. Using before(:all) and
> > before(:each) together just makes things confusing.
> >
> > 2. "it_should_populate_collections" speaks very nicely from a
> > documentation standpoint, but from a failure isolation standpoint
> > (i.e. understanding quickly why something fails when it does) I think
> > it's a bit problematic. There's just no way to understand what's going
> > on in the example without looking at the definition of
> > it_should_populate_collections. It's very clever, but takes attention
> > away from where it should be. The fact that it yields back to the
> > current block makes it even more confusing. I'd stay away from that in
> > specs.
> >
> > In general, I'd encourage you to use custom matchers rather than
> > adding "it_blah" methods (i.e. @model.should populate_collections(:a,
> > :b) ). I think that will make it much easier for other people to look
> > at your specs and understand them from a documentation perspective,
> > and a failure isolation perspective.
> >
> > 2 cents
> >
> > Cheers,
> > David
> > _______________________________________________
> > rspec-users mailing list
> > rspec-users at rubyforge.org
> > http://rubyforge.org/mailman/listinfo/rspec-users
> >
>


More information about the rspec-users mailing list