[rspec-users] What causes "undefined method `each_pair'"?

David Chelimsky dchelimsky at gmail.com
Sat Sep 3 09:25:29 EDT 2011

On Sep 3, 2011, at 5:29 AM, Katrina Owen wrote:

> Hi,
> On Mon, Aug 29, 2011 at 5:33 PM, Nick <nick at deadorange.com> wrote:
>> Hey folks. I've been running into this problem a lot lately, and haven't
>> been able to figure out what's causing it, nor how to fix it:
>> Failure/Error: @importer = SpreadsheetImporter.new @catalog, @excel,
>> @photos_dir
>> NoMethodError:
>>   undefined method `each_pair' for #<Catalog:0xb885f08>
>> # ./app/models/spreadsheet_importer.rb:15:in `initialize'
>> # ./spec/models/spreadsheet_importer_spec.rb:25:in `new'
>> # ./spec/models/spreadsheet_importer_spec.rb:25:in `block (2 levels) in <top
>> (required)>'
>> Line 15 of spreadsheet_importer.rb is:
>> @products_importer  = SpreadsheetImporter::Products.new catalog, spreadsheet
>> Line 25 of spreadsheet_importer_spec.rb is:
>> @importer = SpreadsheetImporter.new @catalog, @excel, @photos_dir
>> What perplexes me is that I've stubbed out the call to
>> SpreadsheetImporter::Products.new , so the class' initializer isn't being
>> called.
>> Here're some more complete code snippets:
>> https://gist.github.com/1178636
> In your gist, the describe block doesn't contain an "it" or "specify"
> block. Only a before(:each).

@Katrina - actually it does contain an example ("it") in the nested group ("describe"). There's just no example in the outer group, but that's a red herring vis a vis this issue.

@Nick - the backtrace tells you the whole story. You just have to look at it.

The problem is that caused when rspec-mocks is asking the incoming Catalog object if it is == to the expected @catalog (as declared in the 'with' clause). rspec-mocks has a feature that allows you to check against a matcher object (i.e. foo.should_receive(:bar).with(hash_including(:a => 'b'))). This is implemented in https://github.com/rspec/rspec-mocks/blob/v2.5.0/lib/rspec/mocks/argument_expectation.rb. rspec-mocks incorrectly thinks the @catalog is a matcher because it responds to :matches? and :description (see https://github.com/rspec/rspec-mocks/blob/v2.5.0/lib/rspec/mocks/argument_expectation.rb#L29), and therefore wraps it in a MatcherMatcher (https://github.com/rspec/rspec-mocks/blob/v2.5.0/lib/rspec/mocks/argument_matchers.rb#L101-109), which asks the @catalog if it matches? the incoming catalog, which mixes in the Mongoid::Matchers module (https://github.com/mongoid/mongoid/blob/c4675a987925a831dc93ab5c525c99dedf46c841/lib/mongoid/matchers.rb#L19-30). That's how we get to `each_pair` on the Catalog.

So the bug is that rspec's definition of what a matcher is is too loose in this case. The problem is that this is RSpec's API for argument matching. If we were to make it more strict now, we'd break unknown numbers of existing specs.

To get around this, you can do this:

SpreadsheetImporter::Products.stub(:new) do |catalog, excel|
  catalog.should eq(@catalog)
  excel.should eq(@excel)

This has the same effect as what you are trying to do (stub SpreadsheetImporter::Products returning @products_importer, and verify the arguments are the right ones).


> Not sure if that's what's causing it, but it would be a place to start.
> Katrina
>> Any help would be greatly appreciated. Thanks!
>> Nick
> -- 
> You received this message because you are subscribed to the Google Groups "rspec" group.
> To post to this group, send email to rspec at googlegroups.com.
> To unsubscribe from this group, send email to rspec+unsubscribe at googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/rspec?hl=en.

More information about the rspec-users mailing list