Skip to content

Instantly share code, notes, and snippets.

@kenji21
Created March 20, 2014 21:31
Show Gist options
  • Save kenji21/9674333 to your computer and use it in GitHub Desktop.
Save kenji21/9674333 to your computer and use it in GitHub Desktop.
//
// DynamicMethodTest.m
// OCMockito
//
// Created by Richard Bergoin on 20/03/14.
// Copyright (c) 2014 Jonathan M. Reid. All rights reserved.
//
#define MOCKITO_SHORTHAND
#import "OCMockito.h"
// Test support
#import "MockTestCase.h"
#import <SenTestingKit/SenTestingKit.h>
#define HC_SHORTHAND
#if TARGET_OS_MAC
#import <OCHamcrest/OCHamcrest.h>
#else
#import <OCHamcrestIOS/OCHamcrestIOS.h>
#endif
#include <objc/runtime.h>
@interface ObjectUnderTest : NSObject
@property (nonatomic, strong) NSMutableDictionary *storage;
@property (nonatomic, strong) NSString *name;
@end
@interface ResolvedMethodUnderTest : ObjectUnderTest
@end
@interface ForwardMethodUnderTest : ObjectUnderTest
@end
@interface DynamicMethodTest : SenTestCase
@end
@implementation ObjectUnderTest
@dynamic name;
- (instancetype)init
{
self = [super init];
if (self) {
_storage = [[NSMutableDictionary alloc] init];
}
return self;
}
@end
static NSString *AttributeNameFromSetSelectorString(NSString *setSelectorString)
{
NSMutableString *attrName = [setSelectorString mutableCopy];
NSString *firstLowercase = [[setSelectorString substringWithRange:NSMakeRange(3, 1)] lowercaseString];
NSInteger objectLen = [@":" length];
[attrName replaceCharactersInRange:NSMakeRange(0, 4) withString:firstLowercase]; // setN -> n
[attrName deleteCharactersInRange:NSMakeRange([attrName length] - objectLen, objectLen)];
return attrName;
}
@implementation ResolvedMethodUnderTest
static id dynamicObjectGetterMethod(id self, SEL _cmd)
{
ObjectUnderTest *object = self;
NSString *attrName = NSStringFromSelector(_cmd);
return [object.storage objectForKey:attrName];
}
static void dynamicObjectSetterMethod(id self, SEL _cmd, id value)
{
ObjectUnderTest *object = self;
NSString *setterName = NSStringFromSelector(_cmd); // setFirstName:
NSString *attrName = AttributeNameFromSetSelectorString(setterName);
if( value )
{
[object.storage setObject:value forKey:attrName];
}
else if( value == nil )
{
[object.storage removeObjectForKey:attrName];
}
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSString *selStr = NSStringFromSelector(sel);
if( [selStr hasPrefix:@"set"] && [selStr hasSuffix:@":"] )
{
const char *types = "v@:@"; // first is return type (see @encode), second is self type, third is _cmd, fourth is param type
return class_addMethod(self, sel, (IMP)dynamicObjectSetterMethod, types);
}
else if( [selStr rangeOfString:@":"].location == NSNotFound )
{
const char *types = "@@:";
return class_addMethod(self, sel, (IMP)dynamicObjectGetterMethod, types);
}
return [super resolveInstanceMethod:sel];
}
@end
@implementation ForwardMethodUnderTest
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector];
if( signature == nil )
{
signature = [[self class] instanceMethodSignatureForSelector:aSelector];
}
return signature;
}
+ (NSMethodSignature *)instanceMethodSignatureForDynamicPropertyWithSelector:(SEL)aSelector
{
NSMethodSignature *signature = nil;
NSString *sel = NSStringFromSelector(aSelector);
if ([sel rangeOfString:@"set"].location == 0)
{
signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
else
{
signature = [NSMethodSignature signatureWithObjCTypes:"@@:"];
}
return signature;
}
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *signature = [super instanceMethodSignatureForSelector:aSelector];
if( signature == nil )
{
signature = [self instanceMethodSignatureForDynamicPropertyWithSelector:aSelector];
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
NSString *selStr = NSStringFromSelector([invocation selector]);
if ([selStr rangeOfString:@"set"].location == 0)
{
NSString *attrName = AttributeNameFromSetSelectorString(selStr);
NSString *obj;
[invocation getArgument:&obj atIndex:2];
[self.storage setObject:obj forKey:attrName];
}
else
{
NSString *attrName = selStr;
NSString *obj = [self.storage objectForKey:attrName];
[invocation setReturnValue:&obj];
}
}
@end
@implementation DynamicMethodTest
- (void)testNumberOfArgumentsOnResolvedGetter
{
ResolvedMethodUnderTest *ut = [[ResolvedMethodUnderTest alloc] init];
NSMethodSignature *getterSignature = [ut methodSignatureForSelector:@selector(name)];
NSInteger num = [getterSignature numberOfArguments];
STAssertNotNil(getterSignature, @"");
STAssertEquals(num, (NSInteger)2, @"");
}
- (void)testNumberOfArgumentsOnForwardGetter
{
ForwardMethodUnderTest *ut = [[ForwardMethodUnderTest alloc] init];
NSMethodSignature *getterSignature = [ut methodSignatureForSelector:@selector(name)];
NSInteger num = [getterSignature numberOfArguments];
STAssertNotNil(getterSignature, @"");
STAssertEquals(num, (NSInteger)2, @"");
}
- (void)testNumberOfArgumentsOnResolvedSetter
{
ResolvedMethodUnderTest *ut = [[ResolvedMethodUnderTest alloc] init];
NSMethodSignature *setterSignature = [ut methodSignatureForSelector:@selector(setName:)];
NSInteger num = [setterSignature numberOfArguments];
STAssertNotNil(setterSignature, @"");
STAssertEquals(num, (NSInteger)3, @"");
}
- (void)testNumberOfArgumentsOnForwardSetter
{
ForwardMethodUnderTest *ut = [[ForwardMethodUnderTest alloc] init];
NSMethodSignature *setterSignature = [ut methodSignatureForSelector:@selector(setName:)];
NSInteger num = [setterSignature numberOfArguments];
STAssertNotNil(setterSignature, @"");
STAssertEquals(num, (NSInteger)3, @"");
}
- (void)testSetFirstNameOnResolved
{
ResolvedMethodUnderTest *ut = [[ResolvedMethodUnderTest alloc] init];
[ut setName:@"Clément"];
STAssertEqualObjects(ut.name, @"Clément", @"");
}
- (void)testSetFirstNameOnResolvedMock
{
ResolvedMethodUnderTest *ut = mock([ResolvedMethodUnderTest class]);
[ut setName:@"Clément"];
[verify(ut) setName:@"Clément"];
}
- (void)testSetFirstNameOnForward
{
ForwardMethodUnderTest *ut = [[ForwardMethodUnderTest alloc] init];
[ut setName:@"Clément"];
STAssertEqualObjects(ut.name, @"Clément", @"");
}
- (void)testSetFirstNameOnForwardMock
{
ForwardMethodUnderTest *ut = mock([ForwardMethodUnderTest class]);
[ut setName:@"Clément"];
[verify(ut) setName:@"Clément"];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment