allending/Kiwi — pull request #401 by epologee (2013-10-21 09:38:14+00:00)
Title: Added a flag to prevent KWCaptureSpy from raising an error when the argument is nil.

This makes it possible to use [[expectFutureValue(spy.argument) should] beNonNil] in asynchronous captures.

For example consider the following (fake) scenario, where the subject under tests performs some asynchronous method which leads to the partial mock to capture a block as an argument.

id partialMock = ...;

KWCaptureSpy *spy = [partialMock captureArgument:@selector(someMethodWithArg:feedback:) atIndex:1];
[[partialMock should] receive:@selector(someMethodWithArg:feedback:) andReturn:nil];

sut = [[SubjectUnderTest alloc] initWithDependency:partialMock];

__block BOOL success = NO;
[sut performAsynchronousMethodWithFeedback:^{
success = YES;
}];

[[expectFutureValue(theValue(NO)) shouldEventuallyBeforeTimingOutAfter(3)] beNo];
[[spy.argument should] beNonNil];

void (^feedback)() = spy.argument;
feedback();

[[theValue(success) should] beYes];

*Please note that in this example, there's no real reason to use spying, the success value could have been subject of an expectFutureValue assertion. However the real scenario that inspired this fake one used spying to modify the arguments of the block being called at the end.*

As you see, there's a bogus timeout assertion in the middle, to pause the test and allow for spy.argument to become something other than nil. If that shouldEventuallyBeforeTimingOutAfter(3) assertion weren't there, the test would fail immediately because KWCaptureSpy raises an exception when the argument is being accessed before the capturing actually happened.

With the modification suggested in this pull request, you can explicitly tell KWCaptureSpy to *not* raise an error when polling for argument, because you know you will be accessing it too early. This makes it possible to rewrite your spec to be just a little bit more obvious:

id partialMock = ...;

KWCaptureSpy *spy = [partialMock captureArgument:@selector(someMethodWithArg:feedback:) atIndex:1];
spy.raiseYetToBeCapturedException = NO;
[[partialMock should] receive:@selector(someMethodWithArg:feedback:) andReturn:nil];

sut = [[SubjectUnderTest alloc] initWithDependency:partialMock];

__block BOOL success = NO;
[sut performAsynchronousMethodWithFeedback:^{
success = YES;
}];

[[expectFutureValue(spy.argument) shouldEventuallyBeforeTimingOutAfter(3)] beNonNil];

void (^feedback)() = spy.argument;
feedback();

[[theValue(success) should] beYes];

I've added a test for the not-raising-an-exception part, but there's no test that shows what the `raiseYetToBeCapturedException` flag can be used for in combination with future values. Cheers.