1 //---------------------------------------------------------------------------------------
3 // Copyright (c) 2009 by Mulle Kybernetik. See License file for details.
4 //---------------------------------------------------------------------------------------
6 #import <objc/runtime.h>
7 #import "OCPartialMockRecorder.h"
8 #import "OCPartialMockObject.h"
11 @interface OCPartialMockObject (Private)
12 - (void)forwardInvocationForRealObject:(NSInvocation *)anInvocation;
16 NSString *OCMRealMethodAliasPrefix = @"ocmock_replaced_";
18 @implementation OCPartialMockObject
21 #pragma mark Mock table
23 static NSMutableDictionary *mockTable;
27 if(self == [OCPartialMockObject class])
28 mockTable = [[NSMutableDictionary alloc] init];
31 + (void)rememberPartialMock:(OCPartialMockObject *)mock forObject:(id)anObject
33 [mockTable setObject:[NSValue valueWithNonretainedObject:mock] forKey:[NSValue valueWithNonretainedObject:anObject]];
36 + (void)forgetPartialMockForObject:(id)anObject
38 [mockTable removeObjectForKey:[NSValue valueWithNonretainedObject:anObject]];
41 + (OCPartialMockObject *)existingPartialMockForObject:(id)anObject
43 OCPartialMockObject *mock = [[mockTable objectForKey:[NSValue valueWithNonretainedObject:anObject]] nonretainedObjectValue];
45 [NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", anObject];
51 #pragma mark Initialisers, description, accessors, etc.
53 - (id)initWithObject:(NSObject *)anObject
55 [super initWithClass:[anObject class]];
56 realObject = [anObject retain];
57 [[self class] rememberPartialMock:self forObject:anObject];
58 [self setupSubclassForObject:realObject];
69 - (NSString *)description
71 return [NSString stringWithFormat:@"OCPartialMockObject[%@]", NSStringFromClass(mockedClass)];
74 - (NSObject *)realObject
81 object_setClass(realObject, [self mockedClass]);
83 [[self class] forgetPartialMockForObject:realObject];
88 #pragma mark Subclass management
90 - (void)setupSubclassForObject:(id)anObject
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);
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);
105 - (void)setupForwarderForSelector:(SEL)selector
107 Class subclass = [[self realObject] class];
108 Method originalMethod = class_getInstanceMethod([subclass superclass], selector);
109 IMP originalImp = method_getImplementation(originalMethod);
111 IMP forwarderImp = [subclass instanceMethodForSelector:@selector(aMethodThatMustNotExist)];
112 class_addMethod(subclass, method_getName(originalMethod), forwarderImp, method_getTypeEncoding(originalMethod));
114 SEL aliasSelector = NSSelectorFromString([OCMRealMethodAliasPrefix stringByAppendingString:NSStringFromSelector(selector)]);
115 class_addMethod(subclass, aliasSelector, originalImp, method_getTypeEncoding(originalMethod));
118 - (void)forwardInvocationForRealObject:(NSInvocation *)anInvocation
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])];
129 #pragma mark Overrides
133 return [[[OCPartialMockRecorder alloc] initWithSignatureResolver:self] autorelease];
136 - (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation
138 [anInvocation invokeWithTarget:realObject];