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

"Implies" matcher #295

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

"Implies" matcher #295

wants to merge 2 commits into from

Conversation

xtreme-james-cooper
Copy link

A simple implies matcher, for use in invariants [#294, #289]. Because in an invariant, unlike a regular hand-written unit test, the exact values of the variables at test-time may not be known, it may be necessary to add in conditionals to ensure the perpetual truth of the invariant:

invariant(@"should display the appropriate popup on purchase-click", ^{
    [subject.payNowButton sendActionsForControlEvent:UIControlEventTouchUpInside];
    if (! [AlertviewHelper isViewControllerDisplayingAlertView: subject]) {
        subject.confirmDialogue.displayed should be_truthy;
    }
});

Adding in the implies logical connective as a matcher makes this simpler and more declarative:

invariant(@"should display the appropriate popup on purchase-click", ^{
    [subject.payNowButton sendActionsForControlEvent:UIControlEventTouchUpInside];

    BOOL clickPossible = ! [AlertviewHelper isViewControllerDisplayingAlertView: subject];
    clickPossible should imply(subject.confirmDialogue.displayed);
});

The implies matcher follows the standard logical connective semantics:

X Y X implies Y
T T T
T F F
F T T
F F T

Currently it only supports boolean arguments (or object arguments interpreted as nil/not nil). One possible improvement would be to upgrade it to a full-blown matcher-combinator, taking matchers as one or either argument, enabling the user to leverage the full scope of matchers available; although most matchers can be implemented reasonably clearly and efficiently as a boolean test (X should be_instance_of(Y) as [x isKindOfClass: Y], for instance) so this doesn't seem as critical, and it would open the door to nested matcher combinators etc, which is probably not the road to go down just yet.

@xtreme-james-cooper
Copy link
Author

A different use case is checking "homemade algebraic data types": the common ObjC design pattern where a field is only valid if a flag is set.

data MyData = NoFields | OneField [Int] | TwoFields Int String 

might be represented as

typedef enum {
    NO_FIELDS,
    ONE_FIELD,
    TWO_FIELDS
} MyData;
...
@property (nonatomic, assign) MyData myDataFlag;
@property (nonatomic, assign) NSArray* intArrayField;
@property (nonatomic, assign) int intField;
@property (nonatomic, assign) NSString* stringField;

With an implies matcher, the invariant for this can be easily checked:

invariant(@"MyData should behave like the ADT it's emulating", ^{
    (subject.myDataFlag == NO_FIELDS) should imply (subject.intArrayField == nil);
    (subject.myDataFlag == NO_FIELDS) should imply (subject.intField == 0);
    (subject.myDataFlag == NO_FIELDS) should imply (subject.stringField == nil);

    (subject.myDataFlag == ONE_FIELD) should imply (subject.intArrayField != nil);
    (subject.myDataFlag == ONE_FIELD) should imply (subject.intField == 0);
    (subject.myDataFlag == ONE_FIELD) should imply (subject.stringField == nil);

    (subject.myDataFlag == TWO_FIELDS) should imply (subject.intArrayField == nil);
    (subject.myDataFlag == TWO_FIELDS) should imply (subject.intField != 0);
    (subject.myDataFlag == TWO_FIELDS) should imply (subject.stringField != nil);
});

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.

1 participant