add support for chainable matchers #189

Closed
wants to merge 17 commits into
from

Projects

None yet

2 participants

@maxbrunsfeld
Contributor

I've added support for creating chainable matchers like this:

    this.addMatchers({
        "toHaveA": function(key) {
            this._valueToCompare = this.actual[key];
            return !!this._valueToCompare;
        },

        "toHaveA between": function(lowerBound) {
            return this._valueToCompare >= lowerBound;
        },

        "toHaveA between and": function(upperBound) {
            return this._valueToCompare <= upperBound;
        }
    });

    expect({ score: 15 }).toHaveA("score").between(1).and(10);

    => "Expected { score : 15 } to have a 'score' between 1 and 10."

You can also do it with a separate argument, like this:

    this.addMatchers("toHaveA", {
        "between": function(lowerBound) {
            return this._valueToCompare >= lowerBound;
        },

        "between and": function(upperBound) {
            return this._valueToCompare <= upperBound;
        }
    });

Currently, the entire chain of matchers produces a single 'pass' or 'fail' in the result set, with a default message constructed from all of the matcher names and arguments. If you set properties on this inside of an earlier matcher, they will be accessible in the later matchers. This allows state to be passed between different matchers in a chain, as is done with the _valueToCompare property in the example above. There are other examples in the specs.

The not property can only be used at the very beginning of the matcher chain. I couldn't think of a consistent way to define the behavior of not when used in the middle of a matcher chain.

Also, custom messages can be defined inside of chained matchers as normal, but they will only be used if they belong to the last matcher in the chain. Otherwise, the default constructed message will be used.

There's very thorough test-coverage of course. I mean this as a sort of conversation-starter. Davis and Rajan, if you have any changes you'd like to see (e.g. changes to the API for adding the matchers, or what results they should produce), I'd like to meet up at work and pair with you guys on it.

maxbrunsfeld added some commits Feb 24, 2012
@maxbrunsfeld maxbrunsfeld Add initial specs for chained matchers ed205e3
@maxbrunsfeld maxbrunsfeld Add jasmine.util.subclass, for creating new subclasses 7b57a0e
@maxbrunsfeld maxbrunsfeld make chainable matcher functions return chained matcher objects
still need to make matcher chains report correct results
03dd1a9
@maxbrunsfeld maxbrunsfeld update old spec for custom matchers
In a matcher function, 'this.message' needs to be a function.
4f7e066
@maxbrunsfeld maxbrunsfeld tweak assertion in custom matchers spec
assert only about particular properties of
expectation results, to allow the ExpectationResult
class to be changed
3ea44cb
@maxbrunsfeld maxbrunsfeld make chained matchers produce correct results and messages
still need to define how to deal with chained
matchers with custom messages
20eab9f
@maxbrunsfeld maxbrunsfeld Add alternative ways of calling #addMatchers:
- addMatchers('matcher name', { matchers })
- addMatchers([ matcher names ], { matchers })

The second signature allows a matcher function to
be used in multiple places in a matcher chain,
without having to add it multiple times.
139749a
@maxbrunsfeld maxbrunsfeld make chained matchers handle custom messages correctly
When matchers are chained, a custom message will
only be used if it is from the last matcher in the
chain.
12e8126
@maxbrunsfeld maxbrunsfeld centralize logic for parsing and generating matcher chain name strings 0632adb
@maxbrunsfeld maxbrunsfeld add one last bit of cleanup missing in last commit 41f3892
@maxbrunsfeld
Contributor

Also, if this gets merged in, I have some ideas for chainable matchers that might be worthy of jasmine core.

maxbrunsfeld added some commits Feb 26, 2012
@maxbrunsfeld maxbrunsfeld create stack trace correctly for chained matchers 569b98a
@maxbrunsfeld maxbrunsfeld put back lines accidentally removed in last commit ffca785
@maxbrunsfeld maxbrunsfeld make chained matchers update passedCount and failedCount correctly ebd6f81
@maxbrunsfeld maxbrunsfeld add 'real life' spec on chainable matchers
By definition, since it passes, this test only
covers the happy path. But it is slightly more 
integrated than the rest of the tests, which assert
about the results of tests in a different jasmine
environment. It also allows you to observe the 
failing behavior of the chainable matchers, by 
messing with the assertions.
337b64a
@maxbrunsfeld maxbrunsfeld further consolidate logic for updating expectation results c8c01cd
@maxbrunsfeld maxbrunsfeld small optimization in Matchers.matcherFn_ c4368eb
@maxbrunsfeld maxbrunsfeld move logic for updating passed and failed counts
Also, add unit test for NestedResults#updateResult.
Previously, this was only tested behaviorally,
through the chained matcher specs.
24862db
@ragaskar
Contributor
ragaskar commented Mar 1, 2012

I like the idea of this; can you collapse these commits into something a little easier to review? Looking at this short commit messages I'd say this is maybe three or four commits (with lengthy commit messages about why the changes in the commit is made).

@maxbrunsfeld
Contributor

I opened another pull request with condensed and better-explained commits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment