Skip to content

Support for invariants #289

@xtreme-james-cooper

Description

@xtreme-james-cooper

A lot of code has some fairly simple invariants - a set of possible UI elements, of which only one can be visible at a time, for instance, or an internal field that can change over the course of use but should never be nil - and it would be nice to quickly verify that none of the use cases in the spec break them, concisely and automatically.

Our quick idea for syntax below:

SPEC_BEGIN(CheckoutViewControllerSpec)

describe(@"CheckoutViewController", ^{

    __block CheckoutViewController* subject;

    invariant(@"only one header label should be shown at at time", ^{
        subject.currentCreditCardLabel.hidden || subject.noCreditCardLabel.hidden should be_truthy;
    });

    beforeEach(^{
        subject = [CheckoutViewController new];
        [subject viewDidLoad];
        [subject viewDidAppear: NO];
     });

    it(@"should not show a pay now button", ^{
        subject.payNowButton.enabled should_not be_truthy;
    });

    context(@"when the user has selected a credit card", ^{
        beforeEach(^{
            [subject creditCardSelected: [[CreditCardModel alloc] initWithNumber:@"1234567890"];
        });

        it(@"should show a pay now button", ^{
            subject.payNowButton.enabled should be_truthy;
        });

        context(@"when the user clicks pay now", ^{
            beforeEach(^{
                [subject.payNowButton sendActionsForControlEvent:UIControlEventTouchUpInside];
            });

            it(@"should show a confirm dialogue", ^{
                subject.confirmDialogue.displayed should be_truthy;
            });

            it(@"should show your redacted card number", ^{
                subject.confirmDialogue.text should equal(@"Do you wish to pay with card xxxxxx7890?");
            });
        });
    });

});

The semantics would be that for every route through the test (as represented by the completion of beforeEach blocks EDIT: probably simpler and clearer just to use contexts and describes) the framework tests all the invariants and treats it as a test failure if any of them is false. In the above example it would test three times: "CheckoutViewController", "CheckoutViewController when the user has selected a credit card" and "CheckoutViewController when the user has selected a credit card when the user clicks pay now" (but only the once, despite the two different tests in the last block).

This would simplify a number of testing patterns and make test suites more robust. (We were inspired by a case similar to the example above, where two labels were superimposed on each other after a complicated flow. We had tests for the flow but only tested the exclusivity of the labels once at the top since it was "clear" that any other case would follow the simple cases. Having invariants would have helped us discover the bug sooner, or at least, having discovered the bug, saved us from an unpleasant surfeit of behavesLike(@"noLabelDuplication"); all over the suite.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions