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

Implement chained (composed) assertions. #329

Closed
wants to merge 12 commits into from

Conversation

eloyesp
Copy link
Contributor

@eloyesp eloyesp commented Sep 30, 2013

The idea is to make possible to have multiple matchers in a sigle expectation.
It does only works with matchers based on BaseMatcher as it define the and
method there.

This idea arised in the issue #280 about make composable matchers.

I've added specs and cucumber features, but I'm almost sure that I've missed
some functionality as the error messages. I can work on those but I want to be
sure that I'm not misdirected.

Can I have some feedback?

@coveralls
Copy link

coveralls commented Sep 30, 2013

Coverage Status

Coverage decreased (-0.22%) when pulling 8d8fb70153312c870a3f6c7bac962e48ad0dac32 on eloyesp:chainned_assertions into a74dee7 on rspec:master.

@coveralls
Copy link

coveralls commented Sep 30, 2013

Coverage Status

Coverage increased (+0.16%) when pulling 0842cf7c15976e31a6e9786d1abcdc8d63df1b83 on eloyesp:chainned_assertions into a74dee7 on rspec:master.

Feature: matcher composition

Matchers can be composed to make several assertions on the same object.
For example we can specify that it should be a Hash and also that it contains
Copy link
Member

@xaviershay xaviershay Oct 1, 2013

Choose a reason for hiding this comment

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

should backtick Hash here to be consistent (or lowercase it)

@xaviershay
Copy link
Member

xaviershay commented Oct 1, 2013

Please add a changelog entry.

@xaviershay
Copy link
Member

xaviershay commented Oct 1, 2013

Nit-picking aside, this looks pretty good to me.

The describe language doesn't quite feel right, I'll have a sleep on that and come up with some suggestions later if no one beats me to the punch.

Thanks for doing this!

@myronmarston
Copy link
Member

myronmarston commented Oct 1, 2013

@eloyesp -- Nice work! This will be a nice building block towards having fully composable matchers. I have some general thoughts/suggestions, and then I'll dig into some specifics in later comments.

  • It would be nice to have or for parity.
  • Rather than and_not, I'm thinking maybe we should just add not as it's own building block, so that you could do be_a(Hash).and(not include(:some_key)). The benefit I see is that not becomes a nice building block on its own; for example, expect(array).to include(not match(/some regex/)) could be used to specify that an array should include something that does not match /some regex/. It could also be used with or if we added that.
  • That said, not is a keyword, and if you use it without an explicit receiver it doesn't send the not message. An alternative method name is not_to, as it reads nicely for a case like expect(k).to be_a(Hash).and(not_to include(:some_key)). That said, it doesn't read very nicely in the include(not_to match(/some regex/)) case. I'm not sure what's best here.
  • It would be nice for users to have a simple way to make there matchers composable as well. To that end, what do you think about moving the definition of and out of BaseMatcher and into a RSpec::Matchers::Composable module that we document as a publicly available module that users can mixin to their matchers to make them composable? I suspect there'll be more that would go into there. Then BaseMatcher can include it.

base_matcher.matches?(actual) and new_matcher.matches?(actual)
end

end
Copy link
Member

@myronmarston myronmarston Oct 1, 2013

Choose a reason for hiding this comment

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

The matcher needs a good, human readable failure message (for both positive and negative cases) and a description. I think you can define it with simple delegation:

def failure_message_for_should
  "#{base_matcher.failure_message_for_should} and #{new_matcher.failure_message_for_should}"
end

# etc for the other methods

(These things would need specs, of course.)

@eloyesp
Copy link
Contributor Author

eloyesp commented Oct 1, 2013

Thank you for your comments, I will work on this ASAP.

end

it "deliberate failure on the second matcher" do
expect({ :foo => 'bar' }).to be_kind_of(Hash).and(include(:not_included))
Copy link
Member

@xaviershay xaviershay Oct 1, 2013

Choose a reason for hiding this comment

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

On a philosophical point, checking the type of an object doesn't feel like good Ruby style, and as such I don't think we should be leading with it in our documentation. Why not use the same example you used in the text above (contains this key but not that key)?

Copy link
Member

@JonRowe JonRowe Oct 1, 2013

Choose a reason for hiding this comment

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

👍

@coveralls
Copy link

coveralls commented Oct 1, 2013

Coverage Status

Coverage increased (+0.16%) when pulling aa6af5b8e93200d14c2d7b0f12a297396602395b on eloyesp:chainned_assertions into a74dee7 on rspec:master.

@coveralls
Copy link

coveralls commented Oct 2, 2013

Coverage Status

Coverage increased (+0.16%) when pulling 95c7f6d6ca16709b95fde58ba3b6f8717acc04e7 on eloyesp:chainned_assertions into a74dee7 on rspec:master.

@coveralls
Copy link

coveralls commented Oct 2, 2013

Coverage Status

Coverage increased (+0.11%) when pulling 6145c8c3159054cb606826cf58674826b517fbf3 on eloyesp:chainned_assertions into a74dee7 on rspec:master.

else
raise ArgumentError
end
end
Copy link
Member

@myronmarston myronmarston Oct 2, 2013

Choose a reason for hiding this comment

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

I generally prefer polymorphism to conditionals such as case statements unless the introduction of an additional class introduces lots of complexity. I don't think it would here, though; I think it would simplify to have an AndComposite and an OrComposite matcher, each of which does the correct thing. Then there's no need for the else case here (or a case statement at all) and the and and or methods can return the appropriate one.

@eloyesp
Copy link
Contributor Author

eloyesp commented Nov 5, 2013

After a lot of thinking I'm almost sure that the 'to_not' side of this matcher should not be implemented. It is not really clear what should mean to negate them.

Also I'm thinking that I have a deeper insight about how to solve the negation problem that @myronmarston presented here.

I think that all the negation responsibility should be moved from the expectation to the matcher

# transforming
expect({a: 'b'}).to_not include(:b)
# into
expect({a: 'b'}).to not_include(:b)

Then, chaining matchers would be written like this:

expect({a: 'b'}).to include(:a).and not_include(:b, :c)

Then not_included, not_matching, etc need only to be decorators for the positive matcher (NegativeMatcher) that turns match? into not_match? and the same with the failure message.

On the other hand I feel that the ExpectationHandler violation of TDA turns out to be an obstacle as I need to implement it again for the CompositeMatcher.

That said I wanted to make clear that this comment have not the intention of offend, as I'm willing to learn (and I already learned a lot) and I think that this is an awesome library. (But I'm feeling now that not being English my native language it may be not understood).

I'm working now on the last commits and can do a re-base if you you think it is necessary.

Also, please feel free to comment any correction on my code or comments. Thanks again.

@myronmarston
Copy link
Member

myronmarston commented Dec 10, 2013

Hey, @eloyesp, thanks for this. I'm finally getting around to adding the rspec-expectations features for rspec 3. I'm going to rebase and squash this and get this merged soon.

eloyesp added 8 commits Dec 11, 2013
- Make feature use ruby 1.8 hashes.
- Fix feature styling issues.
- Make Composable as a module.
It needs refactoring I know...
- Use send instead of public_send as it is not available in ruby 1.8.
Add error messages.
@eloyesp
Copy link
Contributor Author

eloyesp commented Dec 11, 2013

@myronmarston Sorry for the delay but the work didn't left me enough time to work on this. I re-based on master and squashed some minor commits to see if that help.

Thanks for the learning experience, for the comments and for the patience.

@myronmarston
Copy link
Member

myronmarston commented Dec 11, 2013

@myronmarston Sorry for the delay but the work didn't left me enough time to work on this. I re-based on master and squashed some minor commits to see if that help.

Thanks for the learning experience, for the comments and for the patience.

Thank you for taking the time to follow up! I did have one question about 22e0e05 -- see you my comment there.

@myronmarston
Copy link
Member

myronmarston commented Dec 13, 2013

I'm going to close this as I've opened a new PR (#387) that works off of this to provide further improvements. We'll be merging that soon. Your feedback is welcome on my further changes, of course.

myronmarston added a commit that referenced this issue Dec 16, 2013
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.

None yet

5 participants