1 //--------------------------------------------------------------------------------------- 2 // $Id$ 3 // Copyright (c) 2009 by Mulle Kybernetik. See License file for details. 4 //--------------------------------------------------------------------------------------- 5 6 #import <objc/runtime.h> 7 #import "OCPartialMockRecorder.h" 8 #import "OCPartialMockObject.h" 9 10 11 @interface OCPartialMockObject (Private) 12 - (void)forwardInvocationForRealObject:(NSInvocation *)anInvocation; 13 @end 14 15 16 NSString *OCMRealMethodAliasPrefix = @"ocmock_replaced_"; 17 18 @implementation OCPartialMockObject 19 20 21 #pragma mark Mock table 22 23 static NSMutableDictionary *mockTable; 24 25 + (void)initialize 26 { 27 if(self == [OCPartialMockObject class]) 28 mockTable = [[NSMutableDictionary alloc] init]; 29 } 30 31 + (void)rememberPartialMock:(OCPartialMockObject *)mock forObject:(id)anObject 32 { 33 [mockTable setObject:[NSValue valueWithNonretainedObject:mock] forKey:[NSValue valueWithNonretainedObject:anObject]]; 34 } 35 36 + (void)forgetPartialMockForObject:(id)anObject 37 { 38 [mockTable removeObjectForKey:[NSValue valueWithNonretainedObject:anObject]]; 39 } 40 41 + (OCPartialMockObject *)existingPartialMockForObject:(id)anObject 42 { 43 OCPartialMockObject *mock = [[mockTable objectForKey:[NSValue valueWithNonretainedObject:anObject]] nonretainedObjectValue]; 44 if(mock == nil) 45 [NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", anObject]; 46 return mock; 47 } 48 49 50 51 #pragma mark Initialisers, description, accessors, etc. 52 53 - (id)initWithObject:(NSObject *)anObject 54 { 55 [super initWithClass:[anObject class]]; 56 realObject = [anObject retain]; 57 [[self class] rememberPartialMock:self forObject:anObject]; 58 [self setupSubclassForObject:realObject]; 59 return self; 60 } 61 62 - (void)dealloc 63 { 64 if(realObject != nil) 65 [self stop]; 66 [super dealloc]; 67 } 68 69 - (NSString *)description 70 { 71 return [NSString stringWithFormat:@"OCPartialMockObject[%@]", NSStringFromClass(mockedClass)]; 72 } 73 74 - (NSObject *)realObject 75 { 76 return realObject; 77 } 78 79 - (void)stop 80 { 81 object_setClass(realObject, [self mockedClass]); 82 [realObject release]; 83 [[self class] forgetPartialMockForObject:realObject]; 84 realObject = nil; 85 } 86 87 88 #pragma mark Subclass management 89 90 - (void)setupSubclassForObject:(id)anObject 91 { 92 Class realClass = [anObject class]; 93 double timestamp = [NSDate timeIntervalSinceReferenceDate]; 94 const char *className = [[NSString stringWithFormat:@"%@-%p-%f", realClass, anObject, timestamp] UTF8String]; 95 Class subclass = objc_allocateClassPair(realClass, className, 0); 96 objc_registerClassPair(subclass); 97 object_setClass(anObject, subclass); 98 99 Method forwardInvocationMethod = class_getInstanceMethod([self class], @selector(forwardInvocationForRealObject:)); 100 IMP forwardInvocationImp = method_getImplementation(forwardInvocationMethod); 101 const char *forwardInvocationTypes = method_getTypeEncoding(forwardInvocationMethod); 102 class_addMethod(subclass, @selector(forwardInvocation:), forwardInvocationImp, forwardInvocationTypes); 103 } 104 105 - (void)setupForwarderForSelector:(SEL)selector 106 { 107 Class subclass = [[self realObject] class]; 108 Method originalMethod = class_getInstanceMethod([subclass superclass], selector); 109 IMP originalImp = method_getImplementation(originalMethod); 110 111 IMP forwarderImp = [subclass instanceMethodForSelector:@selector(aMethodThatMustNotExist)]; 112 class_addMethod(subclass, method_getName(originalMethod), forwarderImp, method_getTypeEncoding(originalMethod)); 113 114 SEL aliasSelector = NSSelectorFromString([OCMRealMethodAliasPrefix stringByAppendingString:NSStringFromSelector(selector)]); 115 class_addMethod(subclass, aliasSelector, originalImp, method_getTypeEncoding(originalMethod)); 116 } 117 118 - (void)forwardInvocationForRealObject:(NSInvocation *)anInvocation 119 { 120 // in here "self" is a reference to the real object, not the mock 121 OCPartialMockObject *mock = [OCPartialMockObject existingPartialMockForObject:self]; 122 if([mock handleInvocation:anInvocation] == NO) 123 [NSException raise:NSInternalInconsistencyException format:@"Ended up in subclass forwarder for %@ with unstubbed method %@", 124 [self class], NSStringFromSelector([anInvocation selector])]; 125 } 126 127 128 129 #pragma mark Overrides 130 131 - (id)getNewRecorder 132 { 133 return [[[OCPartialMockRecorder alloc] initWithSignatureResolver:self] autorelease]; 134 } 135 136 - (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation 137 { 138 [anInvocation invokeWithTarget:realObject]; 139 } 140 141 142 @end 143