Skip to content

Instantly share code, notes, and snippets.

@Ashton-W
Last active October 10, 2017 15:37
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Ashton-W/19d0025e3ef43ae4c386 to your computer and use it in GitHub Desktop.
Save Ashton-W/19d0025e3ef43ae4c386 to your computer and use it in GitHub Desktop.
Implementation of an XCTestExpectation for delegate calls. More here http://www.ashton-w.net/2016/01/23/Asynchronous-Testing.html
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
NS_ASSUME_NONNULL_BEGIN
@interface ExpectationDelegateProxy : NSProxy
@property (nonatomic) SEL selector;
@property (nonatomic) Protocol *protocol;
@property (nonatomic) XCTestExpectation *expectation;
- (instancetype)initWithProtocol:(Protocol *)protocol selector:(SEL)selector expectation:(XCTestExpectation *)expectation;
@end
NS_ASSUME_NONNULL_END
#import "ExpectationDelegateProxy.h"
#import <objc/runtime.h>
#import <objc/protocol.h>
@implementation ExpectationDelegateProxy
- (instancetype)initWithProtocol:(Protocol *)protocol selector:(SEL)selector expectation:(XCTestExpectation *)expectation
{
NSParameterAssert(protocol);
NSParameterAssert(selector);
NSParameterAssert(expectation);
_protocol = protocol;
_selector = selector;
_expectation = expectation;
return self;
}
#pragma mark - NSProxy
- (void)forwardInvocation:(NSInvocation *)invocation
{
if (sel_isEqual(self.selector, invocation.selector)) {
[self.expectation fulfill];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
// taken from OCMock's OCProtocolMockObject
struct { BOOL isRequired; BOOL isInstance; } opts[4] = { {YES, YES}, {NO, YES}, {YES, NO}, {NO, NO} };
for(int i = 0; i < 4; i++)
{
struct objc_method_description methodDescription = protocol_getMethodDescription(self.protocol, aSelector, opts[i].isRequired, opts[i].isInstance);
if(methodDescription.name != NULL)
return [NSMethodSignature signatureWithObjCTypes:methodDescription.types];
}
return nil;
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol
{
return protocol_conformsToProtocol(self.protocol, aProtocol);
}
- (BOOL)respondsToSelector:(SEL)selector
{
return ([self methodSignatureForSelector:selector] != nil);
}
- (BOOL)isKindOfClass:(Class)aClass
{
return (self.class == aClass);
}
@end
import XCTest
import ObjectiveC.runtime
var ExpectationDelegateProxyAssociationKey: UInt8 = 0
extension XCTestExpectation {
var delegateProxy: ExpectationDelegateProxy {
get {
return objc_getAssociatedObject(self, &ExpectationDelegateProxyAssociationKey) as! ExpectationDelegateProxy
}
set {
objc_setAssociatedObject(self, &ExpectationDelegateProxyAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
extension XCTestCase {
func expectationForDelegate(delegate: Protocol, selector: Selector) -> XCTestExpectation {
let expectation = expectationWithDescription("\(selector) called on \(NSStringFromProtocol(delegate))")
expectation.delegateProxy = ExpectationDelegateProxy(withProtocol: delegate, selector: selector, expectation: expectation);
return expectation;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment