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

Use the differ to print diffs when args are mismatched between expectations and calls #751

Merged
merged 3 commits into from
Oct 20, 2014

Conversation

fables-tales
Copy link
Member

Related #434. This uses the differ that is now in RSpec support to
perform argument diffing when arguments don't match.

Things worth noting/still to do:

  • Special case behaviour needs to be added for the no arguments matcher
    and the any arguments matcher, at the moment something like this:
       @@ -1,2 +1,2 @@
       -[#<RSpec::Mocks::ArgumentMatchers::NoArgsMatcher:0x000001024ec728>]
       +[{:bees=>:foo}]

gets printed, and that's obviously not great.

  • Need to work out what to do in the "similar args" case where there are
    potentially multiple calls. I think the best thing to do would be to
    would be to only diff if there's one call. (@myronmarston maybe has thoughts?)

There's some refactoring that could be done here but I want to finish
implementing the "similar args" implementation before I do that so that
I'm sure that I'm refactoring the right thing.

Also: needs more specs.

end

def diff_message(expected_args, actual_args)
RSpec::Support::Differ.new(:color => true).diff(actual_args, expected_args)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Colour shouldn't be on by default, and having it based off the configuration would simply the spec below too :p

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed...rspec-expectations exposes a color config option if rspec-core isn't loaded, and otherwise simply delegates to rspec-core's config if it is:

https://github.com/rspec/rspec-expectations/blob/bfb7961851451e2775afc88a89ce721d6bf2cdea/lib/rspec/expectations/configuration.rb#L60-L76

@JonRowe
Copy link
Member

JonRowe commented Aug 4, 2014

Looks fairly simple, nice :)

@myronmarston
Copy link
Member

Special case behaviour needs to be added for the no arguments matcher and the any arguments matcher, at the moment something like this:
@@ -1,2 +1,2 @@
-[#RSpec::Mocks::ArgumentMatchers::NoArgsMatcher:0x000001024ec728]
+[{:bees=>:foo}]
gets printed, and that's obviously not great.

Can't this use the description of the arg matcher?

Need to work out what to do in the "similar args" case where there are potentially multiple calls. I think the best thing to do would be to would be to only diff if there's one call. (@myronmarston maybe has thoughts?)

Hmmm...I'd have to see example output from the different options before forming an opinion. Overall, I'd say I'd err on the side of only adding diffing to places where it definitely makes sense; we can always add additional diffing later in the future.

@fables-tales
Copy link
Member Author

I'm gonna be trying to finish this off over the coming weekend \o/

@myronmarston
Copy link
Member

I think you also need to add diff-lcs as a runtime dependency of rspec-mocks.

@JonRowe
Copy link
Member

JonRowe commented Aug 13, 2014

Yep, if we're using the differ from support.

@fables-tales
Copy link
Member Author

I added some specs with output. @myronmarston @JonRowe got thoughts?

@JonRowe
Copy link
Member

JonRowe commented Aug 16, 2014

Looks good to me, except it doesn't seem to work on 1.8.7?

@fables-tales
Copy link
Member Author

1.8.7 is ordering the hashes differently. I'll take a look at it when I get the chance.

@JonRowe
Copy link
Member

JonRowe commented Aug 16, 2014

It's moaning about objects too isn't it? Also on 1.8.7 hashes aren't ordered, we deal with that somewhere by generating the string from the hash so the ordering is the same.


def diff_message(expected_args, actual_args)
formatted_expected_args = expected_args.map do |x|
if x.respond_to?(:description)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking respond_to?(:description) is problematic because it could be something like a BlogPost AR instance that has a description attribute which is a blank string or nil...we can't count on it being a matcher in that case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fables-tales
Copy link
Member Author

I can't get this to fail locally on 1.8.7

@fables-tales
Copy link
Member Author

Yolo pushed a fix.

@fables-tales
Copy link
Member Author

Hey it passed!

@JonRowe
Copy link
Member

JonRowe commented Aug 24, 2014

1.8.7 doesn't like your last commit again :/

@fables-tales
Copy link
Member Author

I had a look locally but I didn't get anywhere concrete. Going to try again tomorrow.

@fables-tales
Copy link
Member Author

@myronmarston good point. I'll do that :)

@fables-tales
Copy link
Member Author

I'm having some trouble debugging this 1.8.7 failure but I'm going to give it another stab on my upcoming holiday (first one in 9 months I am so excite)

@@ -257,7 +257,7 @@ def @double.method_with_default_argument(arg={}); end
expect {
@double.method_with_default_argument(nil)
verify @double
}.to raise_error(RSpec::Mocks::MockExpectationError, "Double \"test double\" received :method_with_default_argument with unexpected arguments\n expected: ({})\n got: (nil)")
}.to raise_error(RSpec::Mocks::MockExpectationError, "Double \"test double\" received :method_with_default_argument with unexpected arguments\n expected: ({})\n got: (nil) \e[0m\n\e[0m\e[34m@@ -1,2 +1,2 @@\n\e[0m\e[31m-[{}]\n\e[0m\e[32m+[nil]\n\e[0m")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diff isn't really the point in this spec, so I'd prefer it not leak into the expectation here as it's brittle. Instead, you can pass a_string_starting_with(...) as the string arg and only include the part of the failure message that was there before.

@myronmarston
Copy link
Member

I kicked the build and left a couple comments.

@myronmarston
Copy link
Member

@samphippen where are you at on this? My 3.1 blog post is almost ready...

@fables-tales
Copy link
Member Author

@myronmarston I'm gonna get this done today whilst travelling to berlin.

@fables-tales
Copy link
Member Author

Gonna have to fix a bunch of rubocop violations now that I've merged master into this.

@fables-tales
Copy link
Member Author

I can't reproduce this jruby failure locally, can anyone take a look at it?

@@ -226,7 +245,7 @@ def arg_list(*args)
end

def arg_has_valid_description(arg)
return false unless arg.respond_to?(:description)
return false unless RSpec::Support.is_a_matcher?(arg)

!arg.description.nil? && !arg.description.empty?
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The !arg.description.nil? && !arg.description.empty? logic is no longer necessary -- it was an earlier attempt to deal with non-matcher args that responded to description, but now that we check if it's a matcher we don't need that logic anymore. I'd just remove the arg_has_valid_description method altogether and replace it with a call to RSpec::Support.is_a_matcher?.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I just remembered that description is an optional part of the rspec matcher interface, so a valid matcher may not respond to description. Thus, I think we actually do need to keep this method (although, it should be renamed to have a ? on the end...), and its logic should be:

RSpec::Support.is_a_matcher?(arg) && arg.respond_to?(:description)

Your diff_message method above should then use this method as well.

@fables-tales
Copy link
Member Author

I've been slowly addressing the feedback. I need to do one more pass and get it mergable but then it should be fine. I'll aim to have it all done this weekend. As a note: I think from now on I'd always prefer in-pull request feedback because github does not collapse commit feedback even if it's been addressed.

@JonRowe
Copy link
Member

JonRowe commented Sep 23, 2014

@samphippen it does, but only if the previous 5 (from memory) lines have changed

@fables-tales
Copy link
Member Author

I'm sorry I haven't gotten to this recently. Some things have gone a little awry at my company and it's keeping me very occupied. If someone else wants to punt this over the finish line, go for it. I'll get to this as soon as I can.

@fables-tales
Copy link
Member Author

@myronmarston it looks like github has collapsed all of your feedback. Can you check this over once more (please leave comments on the PR itself and not the commits). I'm also going to look into this JRuby failure.

This fixes #685. It means that we only print descriptions in errors for
matcher objects and not for user objects.

* Print diffs when arguments in mock expectations are mismatched.
(Sam Phippen, #751)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're changelog entry is in the 3.1.2 section. It should go in the 3.2.0 section.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I think you fixed #685, right? That probably warrants a call-out in the changelog as well.

@fables-tales
Copy link
Member Author

Gonna push to get this done on sunday.

@fables-tales
Copy link
Member Author

@myronmarston how's this looking?

"unexpected arguments\n expected: #{expected_args}\n" \
" got: #{actual_args}"
diff = diff_message(expectation.expected_args, args)
message = "#{intro} received #{expectation.message.inspect} #{unexpected_arguments_message(expected_args, actual_args)}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you split these lines up again? We're looking to reduce our line length, not increase it :P

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Not a merge blocker though.

@JonRowe
Copy link
Member

JonRowe commented Oct 19, 2014

Looks good to me although I'd like to split the lines up to help reduce our overall length.

@myronmarston
Copy link
Member

Thanks, @samphippen! As @JonRowe said it would be good to re-split the lines to keep new or recently edited code under 80 chars as that's where we'd like to get eventually. Not a merge blocker, though, so I'm going to merge and maybe you can follow up by splitting those lines in a separate tiny PR.

myronmarston added a commit that referenced this pull request Oct 20, 2014
Use the differ to print diffs when args are mismatched between expectations and calls
@myronmarston myronmarston merged commit 3da6cdc into master Oct 20, 2014
@myronmarston myronmarston deleted the args-diffing branch October 20, 2014 15:59
@phiggins
Copy link
Contributor

Thanks for this @samphippen, @myronmarston, @JonRowe!

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

Successfully merging this pull request may close these issues.

4 participants