allending/Kiwi — pull request #459 by sharplet (2014-02-26 10:55:53+00:00)
Title: Experimental support for block expectations

So I decided to take a stab at https://github.com/allending/Kiwi/issues/383, and here's what I came up with. The functional test:

```objc
describe(@"block expectations", ^{

describe(@"beCalled matcher", ^{
let(handler, ^{ return block(); });
let(robot, ^{ return [Robot new]; });

it(@"matches if the block is called", ^{
[[handler shouldEventually] beCalled];
[robot speak:@"xyzzy" afterDelay:0 whenDone:handler];
});

it(@"doesn't match if the block isn't called", ^{
[[handler shouldNot] beCalled];
[robot speak:@"yolo" afterDelay:0 whenDone:^{}];
});
});

});
```

A quick rundown of what's happending here:

1. The `block()` macro returns a new `KWBlockProxy` instance which is assigned to the `handler` variable. This class is basically Mike Ash's [`MAFakeBlock` class](https://github.com/mikeash/MABlockForwarding/blob/master/MABlockForwarding.m), converted to ARC, with some minor API changes.

The block proxy behaves like the type of the real block you pass into it. In this example no block is passed in and the default `void (^)(void)` block is used.

It's basically the equivalent of `KWMock`, but for blocks. (I thought the name `KWMockBlock` would be a but much :P.)

2. We use the `beCalled` expectation to create a `KWBlockCallMatcher`. Currently this just flips a `BOOL` when the block invocation is intercepted.

3. When the proxy is invoked, the regular Objective-C message forwarding behaviour is triggered, and we get the block invocation wrapped up nicely as an `NSInvocation`. The `invocationProxy` block provided by `KWBlockCallMatcher` is executed.

4. Just like a receive matcher, the block matcher is verified at the end of the example.

## Rationale

Initially I went down the path of setting up an `expect(...).to` syntax so that I could implement the RSpec-style yield matcher, e.g.,:

```objc
specify(^{
[expect(^(id b){
[robot speak:@"howdy" afterDelay:0 whenDone:b];
}).to invokeBlock];
});
```

However, this didn't really feel very "Kiwi" to me (at least not at the moment). It's not clear if Kiwi will go the `expect(...)` route, in any case. Open to your thoughts on this point. (FYI, I have my `KWExpectation` stuff stashed away, and am happy to push it somewhere if anybody wants to take a look.)

`KWBlockCallMatcher` looks a lot like `KWReceiveMatcher`, and it should be easy to extend it with support for capturing and verifying arguments, stubbing return values, etc..

## Feedback

Given that this is experimental and a bit of a hack, I'd love to know everyone's thoughts on:

- The `KWBlockCallMatcher` "receive"-style API (vs. the expect-style API)
- The `block()` macro API for creating block proxies (Stack Overflow gets the credit for the cool struct initialisation trick)
- Whether this should actually every be merged (!), given that it relies on a private method on `NSInvocation` and having intimate knowledge of the layout of a block in memory (i.e., if it ever changed and `KWBlockProxy` stopped working like a real block, I'm pretty sure I would be incapable of maintaining that code). [MABlockForwarding](https://github.com/mikeash/MABlockForwarding) appears to be unlicensed at the moment, so we'd need to get permission in any case AFAIK.

Thanks to @mikeash for sharing the very cool block proxying code!