Stubbing async actions in Kiwi
Preface: I am well versed in the dark magic that is async testing in Kiwi. This tip is not about writing an async test but, rather, tricking Kiwi into obeying your stubs regardless of the kind of test you're writing. Also, I'm stuck in version 2.0.5 because the Kiwi team did some hilariously bad stuff in a fix version that changed the core operation of stubs. If you have a different version your milage may vary.
Onward!
You might think that doing something like [obj stub:@selector(foo:bar:)]
would be sufficient to stub in Kiwi.
You are wrong.
Don't feel bad. It's not your fault. It's Kiwi's fault.
I'll tell you how to get things back in control.
Imagine you have a method similar to this:
- (void)flingPoo:(NSString *)message
{
dispatch_async(dispatch_get_main_queue(), ^{
[NSNotificationCenter.defaultCenter postNotification:message];
}
}
See how that notification is going out async? Yeah, you see it but Kiwi doesn't. Let's write a test so Kiwi understands. It'd look something like this.
#define MSG @"Hello, gents"
describe(@"flingPoo:", ^{
__block AYMonkey *monkey;
beforeEach(^{
monkey = [AYMonkey new];
[NSNotificationCenter.defaultCenter stub:@selector(postNotification:)];
});
afterEach(^{
monkey = nil;
});
it(@"should fling the message without locking", ^{
[[NSNotificationCenter.defaultCenter shouldEventually] receive:@selector(postNotification:) withArguments:MSG];
[monkey flingPoo:MSG];
});
});
Do you see it? It's subtle.
I've used shouldEventually
instead of should
. Using shouldEventually
triggers Kiwi to run everything expecting an async outcome and, thus, avoid the comedy of tragedy that is notifications leaking out unexpectedly from your specs.
If it helps, think of it as a pseudocode ternary: cmd action = (method.doesSomethingAsync) ? shouldEventually : should;
.
I hope this saves at least one person a few hours of debugging notification-based bugs and crashes in their Kiwi suite. Good luck!