Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ErrorGenerator#arg_list interferes with arg#description methods #685

Closed
olivierlacan opened this issue May 27, 2014 · 10 comments
Closed

ErrorGenerator#arg_list interferes with arg#description methods #685

olivierlacan opened this issue May 27, 2014 · 10 comments

Comments

@olivierlacan
Copy link

I'm having a hard time diagnosing this issue I'm having (running 2.14.6 on Rails 3.1.12).

When I set the following expectation:

expect(SubscriptionMailer).to receive(:cancel_subscription).with(subscription.owner, subscription.next_bill_on)

I receive the following error:

RSpec::Mocks::MockExpectationError: <SubscriptionMailer (class)> received :cancel_subscription with unexpected arguments
  expected: (, Fri, 27 Jun 2014 15:52:06 UTC +00:00)
       got: (#<User id: 1, ...>, "June 27th, 2014")
from /Users/olivierlacan/.rbenv/versions/1.9.3-p545/lib/ruby/gems/1.9.1/gems/rspec-mocks-2.14.6/lib/rspec/mocks/error_generator.rb:144:in `__raise'

If I save the expectation, the expected arguments are indeed:

=> [#<User id: 1, ...>,
 Fri, 27 Jun 2014 15:52:06 UTC +00:00]

Any clue as to what could impact the expectation like that?

@myronmarston
Copy link
Member

What do you mean by "save the expectation"?

@olivierlacan
Copy link
Author

@myronmarston Into a variable.

@olivierlacan
Copy link
Author

Found the issue I think, although the expect output missing the first argument prevented me from noticing it at first:

Fri, 27 Jun 2014 15:52:06 UTC +00:00 vs. `"June 27th, 2014"

@myronmarston I'll let you close but it does seem like weird error feedback.

@myronmarston
Copy link
Member

Can you show an example of what you mean?

@myronmarston
Copy link
Member

@myronmarston I'll let you close but it does seem like weird error feedback.

It's still unclear to me what the issue is you are reporting. I'm not trying to be obtuse...can you paste a reproducible example?

@olivierlacan
Copy link
Author

Only way I could reproduce this was with an ActiveRecord object.

the_object = double("the object")
subscription = double("the subscription")
owner = User.create # had to use an AR model here, didn't work with doubles

allow(subscription).to receive(:owner).and_return(owner)
allow(subscription).to receive(:next_charge_on).and_return(1.month.from_now.in_time_zone)

expect(the_object).to receive(:die).with(subscription.owner, subscription.next_charge_on)

subscription_with_weird_date = double("a subscription", next_charge_on: 1.month.from_now.in_time_zone.to_date.to_formatted_s(:long_ordinal))

the_object.die(owner, subscription_with_weird_date.next_charge_on)

Here, subscription_with_weird_date represents a piece of production code which changes the behavior of next_charge_on so that the spec is now out of date.

This is the output:

RSpec::Mocks::MockExpectationError: Double "the object" received :die with unexpected arguments
  expected: (, Fri, 27 Jun 2014 18:46:25 UTC +00:00)
       got: (#<User id: 5, ...>, "June 27th, 2014")
from /Users/olivierlacan/.rbenv/versions/1.9.3-p545/lib/ruby/gems/1.9.1/gems/rspec-mocks-2.14.6/lib/rspec/mocks/error_generator.rb:144:in `__raise'

@olivierlacan
Copy link
Author

Welp, figured it out further. Our User class defines a description instance method and while rspec-mocks' ErrorGenerator class tests the expected arguments to see if they respond to description. My User argument does, but the return value in this context is "".

Maybe rspec-mocks shouldn't expect such a common method name? Why not mock_description?

See:

args.collect {|arg| arg_has_valid_description(arg) ? arg.description : arg.inspect }.join(", ")

@myronmarston
Copy link
Member

Maybe rspec-mocks shouldn't expect such a common method name? Why not mock_description?

Because rspec-mocks supports any custom matcher (using the matcher protocol defined by rspec-expectations) for with, and the method on the matcher protocol in description.

You're right that using description is problematic, though. If I had realized this issue earlier, I probably would have made the change to rspec_description (rather than description) as part of the matcher protocol changes that went into 3.0. At this point, we're at 3.0.0.rc1 and planning to release 3.0 final this weekend, and even though this is an outstanding issue, I don't think that we should change it -- it would cause more pain to make 3rd parties update their matchers again, rather than dealing with this relatively rare edge case.

I've got some thoughts on how to fix it, though: we can do a check using a method like this:

https://github.com/rspec/rspec-expectations/blob/1dce0c78b1a19f0d328d9574d7b3e4201e269b3f/lib/rspec/matchers.rb#L908-L915

...and only use description if the object appears to be a matcher.

@olivierlacan olivierlacan changed the title Mock mutation? ErrorGenerator#arg_list interferes with arg#description methods May 27, 2014
@olivierlacan
Copy link
Author

@myronmarston That sounds like a good non-API breaking way to do that. I agree with you, it's pretty edgy case even if I can see it bitting other people unexpectedly.

Want me to submit a PR? Or you want to tackle it?

@myronmarston
Copy link
Member

Want me to submit a PR? Or you want to tackle it?

You are welcome to :). It's a bit more complicated then it appears at first, though:

  • The is_a_matcher? method is in rspec-expectations but this is rspec-mocks and it can't rely on that. We should probably move it to rspec-support so it can be used from both gems.
  • The argument matchers in rspec-mocks' are not full matchers in the sense that rspec-expectations looks for -- so we'll have to do something about them so that is_a_matcher? returns true for them. One idea is to do Reconcile duplicatated (but slightly different) matchers between rspec-mocks and rspec-expectations #513, and move the arg matchers into rspec-support and then rspec-mocks and rspec-expectations can both use them, and they'd be a full rspec-expectations matcher in order to be usable there.

fables-tales pushed a commit that referenced this issue Sep 16, 2014
fables-tales pushed a commit that referenced this issue Sep 16, 2014
This fixes #685. It means that we only print descriptions in errors for
matcher objects and not for user objects.
fables-tales pushed a commit that referenced this issue Sep 19, 2014
This fixes #685. It means that we only print descriptions in errors for
matcher objects and not for user objects.
jsonn pushed a commit to jsonn/pkgsrc that referenced this issue Feb 8, 2015
### 3.2.0 / 2015-02-03
[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.1.3...v3.2.0)

Enhancements:

* Treat `any_args` as an arg splat, allowing it to match an arbitrary
  number of args at any point in an arg list. (Myron Marston, #786)
* Print diffs when arguments in mock expectations are mismatched.
  (Sam Phippen, #751)
* Support names for verified doubles (`instance_double`, `instance_spy`,
  `class_double`, `class_spy`, `object_double`, `object_spy`). (Cezary
  Baginski, #826)
* Make `array_including` and `hash_including` argument matchers composable.
  (Sam Phippen, #819)
* Make `allow_any_instance_of(...).to receive(...).and_wrap_original`
  work. (Ryan Fitzgerald, #869)

Bug Fixes:

* Provide a clear error when users wrongly combine `no_args` with
  additional arguments (e.g. `expect().to receive().with(no_args, 1)`).
  (Myron Marston, #786)
* Provide a clear error when users wrongly use `any_args` multiple times in the
  same argument list (e.g. `expect().to receive().with(any_args, 1, any_args)`.
  (Myron Marston, #786)
* Prevent the error generator from using user object #description methods.
  See [#685](rspec/rspec-mocks#685).
  (Sam Phippen, #751)
* Make verified doubles declared as `(instance|class)_double(SomeConst)`
  work properly when `SomeConst` has previously been stubbed.
  `(instance|class)_double("SomeClass")` already worked properly.
  (Myron Marston, #824)
* Add a matcher description for `receive`, `receive_messages` and
  `receive_message_chain`. (Myron Marston, #828)
* Validate invocation args for null object verified doubles.
  (Myron Marston, #829)
* Fix `RSpec::Mocks::Constant.original` when called with an invalid
  constant to return an object indicating the constant name is invalid,
  rather than blowing up. (Myron Marston, #833)
* Make `extend RSpec::Mocks::ExampleMethods` on any object work properly
  to add the rspec-mocks API to that object. Previously, `expect` would
  be undefined. (Myron Marston, #846)
* Fix `require 'rspec/mocks/standalone'` so that it only affects `main`
  and not every object. It's really only intended to be used in a REPL
  like IRB, but some gems have loaded it, thinking it needs to be loaded
  when using rspec-mocks outside the context of rspec-core.
  (Myron Marston, #846)
* Prevent message expectations from being modified by customization methods
  (e.g. `with`) after they have been invoked. (Sam Phippen and Melanie Gilman, #837)
* Handle cases where a method stub cannot be removed due to something
  external to RSpec monkeying with the method definition. This can
  happen, for example, when you `file.reopen(io)` after previously
  stubbing a method on the `file` object. (Myron Marston, #853)
* Provide a clear error when received message args are mutated before
  a `have_received(...).with(...)` expectation. (Myron Marston, #868)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants