Skip to content
Brian Gesiak edited this page Apr 5, 2014 · 31 revisions

Specs | Expectations | Mocks and Stubs | Asynchronous Testing

Expectations

Expectations are the language you use to verify object behavior in your examples. An expectation has the form [[subject should] someCondition:anArgument], where [subject should] is the type of expectation and ... someCondition:anArgument] is the matcher expression.

Example:

    id car = [Car car];
    [[car shouldNot] beNil];
    [[car should] beKindOfClass:[Car class]];
    [[car shouldNot] conformToProtocol:@protocol(FlyingMachine)];
    [[[car should] have:4] wheels];
    [[theValue(car.speed) should] equal:theValue(42.0f)];
    [[[car should] receive] changeToGear:3];

should and shouldNot

[subject should] and [subject shouldNot] are expressions that prepare a receiver to receive an expectation matcher. They are followed by the actual match expression that is to be evaluated.

By default, subject guards are also created when [subject should ] and [subject shouldNot] are used. A message sent to nil would normally do nothing. However, you rarely want an expectation to pass trivially just because the subject is nil, so these expressions set up guards that make the example fail if they are left unacknowledged.

Wrapping Scalars

With few exceptions, the parameters to matcher expressions are always objects (ids). Wherever a scalar (int, float, etc.) value needs to (and can) be used where an id is expected, it should be wrapped using the theValue(aScalar) macro. This is also the mechanism to use when a scalar value needs to be the subject of an expectation, and when the value of a stub needs to be a scalar.

Example:

    [[theValue(1 + 1) should] equal:theValue(2)];
    [[theValue(YES) shouldNot] equal:theValue(NO)];
    [[theValue(20u) should] beBetween:theValue(1) and:theValue(30.0)];
    [[theValue(animal.mass) should] beGreaterThan:theValue(42.0f)]

Message Patterns

Some Kiwi matchers supports expectations that use message patterns. When message patterns are used, they show up last in an expectation, and look like a message that would be a valid message to the subject. In the example below, jumpToStarSystemWithIndex:3 is the message pattern.

Note that some caveats apply when using message patterns.

Example:

    [[[cruiser should] receive] jumpToStarSystemWithIndex:3];
    [cruiser jumpToStarSystemWithIndex:3];

Expectations: Values and Numerics

  • [[subject shouldNot] beNil]
  • [[subject should] beNil]
  • [[subject should] beIdenticalTo:(id)anObject] - compares id's
  • [[subject should] equal:(id)anObject]
  • [[subject should] equal:(double)aValue withDelta:(double)aDelta]
  • [[subject should] beWithin:(id)aDistance of:(id)aValue]
  • [[subject should] beLessThan:(id)aValue]
  • [[subject should] beLessThanOrEqualTo:(id)aValue]
  • [[subject should] beGreaterThan:(id)aValue]
  • [[subject should] beGreaterThanOrEqualTo:(id)aValue]
  • [[subject should] beBetween:(id)aLowerEndpoint and:(id)anUpperEndpoint]
  • [[subject should] beInTheIntervalFrom:(id)aLowerEndpoint to:(id)anUpperEndpoint]
  • [[subject should] beTrue]
  • [[subject should] beFalse]
  • [[subject should] beYes]
  • [[subject should] beNo]
  • [[subject should] beZero]

Expectations: Substring Matching

  • [[subject should] containString:(NSString*)substring]
  • [[subject should] containString:(NSString*)substring options:(NSStringCompareOptions)options]
  • [[subject should] startWithString:(NSString*)prefix]
  • [[subject should] endWithString:(NSString*)suffix]

Example:

    [[@"Hello, world!" should] containString:@"world"];
    [[@"Hello, world!" should] containString:@"WORLD" options:NSCaseInsensitiveSearch];
    [[@"Hello, world!" should] startWithString:@"Hello,"];
    [[@"Hello, world!" should] endWithString:@"world!"];

Expectations: Regular-Expression Pattern Matching

  • [[subject should] matchPattern:(NSString*)pattern]
  • [[subject should] matchPattern:(NSString*)pattern options:(NSRegularExpressionOptions)options]
    [[@"ababab" should] matchPattern:@"(ab)+"];
    [[@" foo " shouldNot] matchPattern:@"^foo$"];
    [[@"abABab" should] matchPattern:@"(ab)+" options:NSRegularExpressionCaseInsensitive];

Expectations: Count changes

  • [[theBlock(^{ ... }) should] change:^{ return (NSInteger)count; }]
  • [[theBlock(^{ ... }) should] change:^{ return (NSInteger)count; } by:+1]
  • [[theBlock(^{ ... }) should] change:^{ return (NSInteger)count; } by:-1]

Example:

    [[theBlock(^{
        [array addObject:@"foo"];
    }) should] change:^{ return (NSInteger)[array count]; } by:+1];
  
    [[theBlock(^{
        [array addObject:@"bar"];
        [array removeObject:@"foo"];
    }) shouldNot] change:^{ return (NSInteger)[array count]; }];

    [[theBlock(^{
        [array removeObject:@"bar"];
    }) should] change:^{ return (NSInteger)[array count]; } by:-1];

Expectations: Object Testing

  • [[subject should] beKindOfClass:(Class)aClass]
  • [[subject should] beMemberOfClass:(Class)aClass]
  • [[subject should] conformToProtocol:(Protocol *)aProtocol]
  • [[subject should] respondToSelector:(SEL)aSelector]

Expectations: Collections

With collection subjects:

  • [[subject should] beEmpty]
  • [[subject should] contain:(id)anObject]
  • [[subject should] containObjectsInArray:(NSArray *)anArray]
  • [[subject should] containObjects:(id)firstObject, ...]
  • [[subject should] haveCountOf:(NSUInteger)aCount]
  • [[subject should] haveCountOfAtLeast:(NSUInteger)aCount]
  • [[subject should] haveCountOfAtMost:(NSUInteger)aCount]

With collection keys:

  • [[[subject should] have:(NSUInteger)aCount] collectionKey]
  • [[[subject should] haveAtLeast:(NSUInteger)aCount] collectionKey]
  • [[[subject should] haveAtMost:(NSUInteger)aCount] collectionKey]

If subject is a collection (e.g. NSArray), collectionKey can be anything since it is just syntactic sugar (items, for example). Otherwise, collectionKey should be a message that can be sent to subject and returns the collection to be checked against.

Example:

    NSArray *array = [NSArray arrayWithObject:@"foo"];
    [[array should] have:1] item];
    
    Car *car = [Car car];
    [car setPassengers:[NSArray arrayWithObjects:@"Eric", "Stan", nil]];
    [[[[car passengers] should] haveAtLeast:2] items];
    [[[car should] haveAtLeast:2] passengers];

Expectations: Interactions and Messages

These expectations verify that specific messages are received by a subject between the period the expectation was created and the end of an example. These expectations also stub the specified selector or message pattern, so the original implementation will not be executed.

These expectations can be set on both real objects and mocks, but some caveats apply when setting receive expectations.

Selector with Any Arguments:

  • [[subject should] receive:(SEL)aSelector]
  • [[subject should] receive:(SEL)aSelector withCount:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector withCountAtLeast:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector withCountAtMost:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCount:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtLeast:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtMost:(NSUInteger)aCount]

Selector with Specific Arguments:

  • [[subject should] receive:(SEL)aSelector withArguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector withCount:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector withCountAtLeast:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector withCountAtMost:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withArguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCount:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtLeast:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtMost:(NSUInteger)aCount arguments:(id)firstArgument, ...]

Example:

subject = [Cruiser cruiser];
[[subject should] receive:@selector(energyLevelInWarpCore:) 
    andReturn:theValue(42.0f) withCount:2 arguments:theValue(7)];
[subject energyLevelInWarpCore:7];
float energyLevel = [subject energyLevelInWarpCore:7];
[[theValue(energyLevel) should] equal:theValue(42.0f)];

Note that you can replace arguments with the any() wild-card. This is useful if you only care about some arguments:

id subject = [Robot robot];
[[subject should] receive:@selector(speak:afterDelay:whenDone:) withArguments:@"Hello world",any(),any()];
[subject speak:@"Hello world" afterDelay:3 whenDone:nil];

Message Pattern Form:

  • [[[subject should] receive] *messagePattern*]
  • [[[subject should] receiveWithCount:(NSUInteger)aCount] *messagePattern*]
  • [[[subject should] receiveWithCountAtLeast:(NSUInteger)aCount] *messagePattern*]
  • [[[subject should] receiveWithCountAtMost:(NSUInteger)aCount] *messagePattern*]
  • [[[subject should] receiveAndReturn:(id)aValue] *messagePattern*]
  • [[[subject should] receiveAndReturn:(id)aValue withCount:(NSUInteger)aCount] *messagePattern*]
  • [[[subject should] receiveAndReturn:(id)aValue withCountAtLeast:(NSUInteger)aCount] *messagePattern*]
  • [[[subject should] receiveAndReturn:(id)aValue withCountAtMost:(NSUInteger)aCount] *messagePattern*]

Example:

    id cruiser = [Cruiser cruiser];
    [[[cruiser should] receiveAndReturn:theValue(42.0f) withCount:2] energyLevelInWarpCore:7];
    [cruiser energyLevelInWarpCore:7];
    float energyLevel = [cruiser energyLevelInWarpCore:7];
    [[theValue(energyLevel) should] equal:theValue(42.0f)];

Expectations: Notifications

  • [[@"MyNotification" should] bePosted];
  • [[@"MyNotification" should] bePostedWithObject:(id)object];
  • [[@"MyNotification" should] bePostedWithUserInfo:(NSDictionary *)userInfo];
  • [[@"MyNotification" should] bePostedWithObject:(id)object andUserInfo:(NSDictionary *)userInfo];
  • [[@"MyNotification" should] bePostedEvaluatingBlock:^(NSNotification *note)block];

Example:

    [[@"MyNotification" should] bePosted];

    NSNotification *myNotification = [NSNotification notificationWithName:@"MyNotification"
                                                                   object:nil];
    [[NSNotificationCenter defaultCenter] postNotification:myNotification];

Expectations: Async Calls

  • [[subject shouldEventually] receive:(SEL)aSelector]
  • [[subject shouldEventually] receive:(SEL)aSelector withArguments:(id)firstArgument, ...]

Expectations: Exceptions

  • [[theBlock(^{ ... }) should] raise]
  • [[theBlock(^{ ... }) should] raiseWithName:]
  • [[theBlock(^{ ... }) should] raiseWithReason:(NSString *)aReason]
  • [[theBlock(^{ ... }) should] raiseWithName:(NSString *)aName reason:(NSString *)aReason]

Example:

    [[theBlock(^{
        [NSException raise:@"FooException" reason:@"Bar-ed"];
    }) should] raiseWithName:@"FooException" reason:@"Bar-ed"];

Custom Matchers

The easiest way to create a custom matcher in Kiwi is to subclass KWMatcher and override the methods in the code snippet below as appropriate.

To make your matcher available in your spec, use registerMatchers(namespacePrefix) in your specs.

Looking at some matchers in the Kiwi sources will be more instructive (KWEqualMatcher, etc.).

Example:

    // Snippet from AnimalTypeMatcher.m
    
    #pragma mark Getting Matcher Strings
    
    // REQUIRED: Return an array of selector strings for the expectations this
    // matcher is used for.
    //
    // For example, this matcher handles [[subject should] beTypeOfMammal:] and
    // [[subject should] beTypeOfInsect:].
    + (NSArray *)matcherStrings {
        return [NSArray arrayWithObjects:@"beTypeOfMammal:", @"beTypeOfInsect:", nil];
    }
    
    #pragma mark Matching
    
    // REQUIRED: Evaluate the predicate here.
    // self.subject is available automatically.
    // self.otherSubject is a member variable you would have declared yourself.
    - (BOOL)evaluate {
        return [[self.subject animalType] isEqual:self.otherSubject];
    }
    
    #pragma mark Getting Failure Messages
    
    // REQUIRED: Return a custom error message for when "should" is used.
    - (NSString *)failureMessageForShould {
        return @"expected subject to be an animal or insect";
    }
    
    // OPTIONAL: If you don't override this, Kiwi uses -failureMessageForShould: and
    // replaces the first "to" with "not to".
    - (NSString *)failureMessageForShouldNot {
        return @"expected subject not to be an animal or insect";
    }
    
    #pragma mark Configuring Matchers
    
    // These methods should correspond to the selector strings returned in +matcherStrings.
    //
    // Use them to finish configuring your matcher so that -evaluate can be called
    // successfully later. Being a subclass of KWMatcher handles other details like
    // setting up self.subject.
    
    - (void)beTypeOfMammal:(id)anObject {
      self.otherSubject = anObject;
    }
    
    - (void)beTypeOfInsect:(id)anObject {
      self.otherSubject = anObject;
    }