Skip to content
akuraru edited this page Feb 6, 2014 · 11 revisions

Specs | Expectations | Mocks and Stubs | Asynchronous Testing

Mocks

Mocks are objects that imitate a class, or look like they conform to a protocol. They let you focus on interaction behavior between objects before full implementations exist, and to isolate objects from dependencies that may be too expensive or unreliable to use when running specs.

	id carMock = [Car mock];
	[ [carMock should] beMemberOfClass:[Car class]];
	[ [carMock should] receive:@selector(currentGear) andReturn:theValue(3)];
	[ [theValue(carMock.currentGear) should] equal:theValue(3)];
	
	id carNullMock = [Car nullMock];
	[ [theValue(carNullMock.currentGear) should] equal:theValue(0)];
	[carNullMock applyBrakes];
	
	id flyerMock = [KWMock mockForProtocol:@protocol(FlyingMachine)];
	[ [flyerMock should] conformToProtocol:@protocol(FlyingMachine)];
	[flyerMock stub:@selector(dragCoefficient) andReturn:theValue(17.0f)];
	
	id flyerNullMock = [KWMock nullMockForProtocol:@protocol(FlyingMachine)];
	[flyerNullMock takeOff];

Null Mocks

A plain mock object will raise an exception when it receives a selector or message pattern that it does not expect. Expected messages are automatically added to a mock when stub or receive expectations are used on a mock.

If you don't care about mocks receiving other messages, and don't want exceptions to be raised, then use a null mock (also know as a null object).

Class Mocks

Creating Class mocks (NSObject Category):

  • [SomeClass mock]
  • [SomeClass mockWithName:(NSString *)aName]
  • [SomeClass nullMock]
  • [SomeClass nullMockWithName:(NSString *)aName]

Creating Class mocks:

  • [KWMock mockForClass:(Class)aClass]
  • [KWMock mockWithName:(NSString *)aName forClass:(Class)aClass]
  • [KWMock nullMockForClass:(Class)aClass]
  • [KWMock nullMockWithName:(NSString *)aName forClass:(Class)aClass]

Protocol Mocks

Creating Protocol Mocks:

  • [KWMock mockForProtocol:(Protocol *)aProtocol]
  • [KWMock mockWithName:(NSString *)aName forProtocol:(Protocol *)aProtocol]
  • [KWMock nullMockForProtocol:(Protocol *)aProtocol]
  • [KWMock nullMockWithName:(NSString *)aName forProtocol:(Protocol *)aProtocol]

Stubs

Stubs return canned responses on selectors or message patterns. Kiwi lets you stub methods on both real objects (including Class objects) and mocks. Stubs that do not have values defined will return nil, 0, or write zeros to the return value. Stubs that return scalars need to have the value wrapped with theValue(aValue).

All stubs are always cleared at the end of a spec example (an it block).

Note that some caveats apply when using stubs (see last section on this page).

Stubbing selectors:

  • [subject stub:(SEL)aSelector]
  • [subject stub:(SEL)aSelector andReturn:(id)aValue]

Stubbing message patterns:

  • [ [subject stub] *messagePattern*]
  • [ [subject stubAndReturn:(id)aValue] *messagePattern*]

Example:

	id cruiser = [Cruiser cruiser];
	[ [cruiser stubAndReturn:theValue(42.0f)] energyLevelInWarpCore:7];
	float energyLevel = [cruiser energyLevelInWarpCore:7];
	[ [theValue(energyLevel) should] equal:theValue(42.0f)];
	
	[Cruiser stub:@selector(classification) andReturn:@"Not a moon"];
	[ [ [Cruiser classification] should] equal:@"Not a moon"];
	
	id mock = [Animal mock];
	[mock stub:@selector(species) andReturn:@"P. tigris"];
	[ [mock.species should] equal:@"P. tigris"];

Capturing Arguments

At times, you may want to capture an argument that is passed to mock object. For instance, the argument may be an object that did not implement isEqual: and it must be captured in order to be verified properly. Alternatively, and the most likely case, is that the mock receives a message where one of the arguments is a block and it must be captured and invoked in order to verify the behavior of that block.

Example:

	id robotMock = [KWMock nullMockForClass:[Robot class]];
	KWCaptureSpy *spy = [robotMock captureArgument:@selector(speak:afterDelay:whenDone:) atIndex:2];
	
	[[[robotMock should] receive] speak:@"Goodbye"];
	
	[robotMock speak:@"Hello" afterDelay:2 whenDone:^{
		[robotMock speak:@"Goodbye"];
	}];
	
	void (^block)(void) = spy.argument;
	block();

Advanced Stubs and Memory Management

One day, the need might arrive for you to stub methods like alloc. It is probably a bad idea, but if you insist, Kiwi does support it. Be forewarned that doing this correctly requires thinking through subtle issues such as how initialization will be handled.

Kiwi stubs conform to Objective-C memory management conventions. When a stub writes an object return value and has a selector that begins with alloc, begins with new, or contains copy, a retain will be sent to the value by the stub before returning.

Therefore, callers should not treat objects returned from stubbed methods differently from regular methods as far as memory management is concerned.

Caveats

Kiwi makes heavy use of Objective-C runtime mechanisms, including message forwarding (e.g. forwardInvocation:). Because Kiwi needs to make certain assumptions on what methods can safely be called to do what it does, there are some conventions that you have to follow.

To keep things simple and sane, certain methods/selectors must never be used in message patterns, receive expectations, be stubbed, or otherwise have their normal behavior modified. Using these selectors is unsupported and will probably result in very strange behavior.

In practice, for high level application code, you probably won't need to worry about this, but it is a good idea to keep these rules in the back of your mind.

Blacklist (not safe to use):

  • All NSObject class and NSObject protocol methods not in the whitelist (e.g. -class, -superclass, -retain, -release, etc.).
  • All Kiwi objects and methods.

Whitelist (safe to use):

  • +alloc
  • +new
  • +copy
  • -copy
  • -mutableCopy
  • -isEqual:
  • -description
  • -hash
  • -init
  • Any other method not in the NSObject class and NSObject protocol.