| //--------------------------------------------------------------------------------------- |
| // $Id$ |
| // Copyright (c) 2009 by Mulle Kybernetik. See License file for details. |
| //--------------------------------------------------------------------------------------- |
| |
| #import <objc/runtime.h> |
| #import "OCPartialMockRecorder.h" |
| #import "OCPartialMockObject.h" |
| |
| |
| @interface OCPartialMockObject (Private) |
| - (void)forwardInvocationForRealObject:(NSInvocation *)anInvocation; |
| @end |
| |
| |
| NSString *OCMRealMethodAliasPrefix = @"ocmock_replaced_"; |
| |
| @implementation OCPartialMockObject |
| |
| |
| #pragma mark Mock table |
| |
| static NSMutableDictionary *mockTable; |
| |
| + (void)initialize |
| { |
| if(self == [OCPartialMockObject class]) |
| mockTable = [[NSMutableDictionary alloc] init]; |
| } |
| |
| + (void)rememberPartialMock:(OCPartialMockObject *)mock forObject:(id)anObject |
| { |
| [mockTable setObject:[NSValue valueWithNonretainedObject:mock] forKey:[NSValue valueWithNonretainedObject:anObject]]; |
| } |
| |
| + (void)forgetPartialMockForObject:(id)anObject |
| { |
| [mockTable removeObjectForKey:[NSValue valueWithNonretainedObject:anObject]]; |
| } |
| |
| + (OCPartialMockObject *)existingPartialMockForObject:(id)anObject |
| { |
| OCPartialMockObject *mock = [[mockTable objectForKey:[NSValue valueWithNonretainedObject:anObject]] nonretainedObjectValue]; |
| if(mock == nil) |
| [NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", anObject]; |
| return mock; |
| } |
| |
| |
| |
| #pragma mark Initialisers, description, accessors, etc. |
| |
| - (id)initWithObject:(NSObject *)anObject |
| { |
| [super initWithClass:[anObject class]]; |
| realObject = [anObject retain]; |
| [[self class] rememberPartialMock:self forObject:anObject]; |
| [self setupSubclassForObject:realObject]; |
| return self; |
| } |
| |
| - (void)dealloc |
| { |
| if(realObject != nil) |
| [self stop]; |
| [super dealloc]; |
| } |
| |
| - (NSString *)description |
| { |
| return [NSString stringWithFormat:@"OCPartialMockObject[%@]", NSStringFromClass(mockedClass)]; |
| } |
| |
| - (NSObject *)realObject |
| { |
| return realObject; |
| } |
| |
| - (void)stop |
| { |
| object_setClass(realObject, [self mockedClass]); |
| [realObject release]; |
| [[self class] forgetPartialMockForObject:realObject]; |
| realObject = nil; |
| } |
| |
| |
| #pragma mark Subclass management |
| |
| - (void)setupSubclassForObject:(id)anObject |
| { |
| Class realClass = [anObject class]; |
| double timestamp = [NSDate timeIntervalSinceReferenceDate]; |
| const char *className = [[NSString stringWithFormat:@"%@-%p-%f", realClass, anObject, timestamp] UTF8String]; |
| Class subclass = objc_allocateClassPair(realClass, className, 0); |
| objc_registerClassPair(subclass); |
| object_setClass(anObject, subclass); |
| |
| Method forwardInvocationMethod = class_getInstanceMethod([self class], @selector(forwardInvocationForRealObject:)); |
| IMP forwardInvocationImp = method_getImplementation(forwardInvocationMethod); |
| const char *forwardInvocationTypes = method_getTypeEncoding(forwardInvocationMethod); |
| class_addMethod(subclass, @selector(forwardInvocation:), forwardInvocationImp, forwardInvocationTypes); |
| } |
| |
| - (void)setupForwarderForSelector:(SEL)selector |
| { |
| Class subclass = [[self realObject] class]; |
| Method originalMethod = class_getInstanceMethod([subclass superclass], selector); |
| IMP originalImp = method_getImplementation(originalMethod); |
| |
| IMP forwarderImp = [subclass instanceMethodForSelector:@selector(aMethodThatMustNotExist)]; |
| class_addMethod(subclass, method_getName(originalMethod), forwarderImp, method_getTypeEncoding(originalMethod)); |
| |
| SEL aliasSelector = NSSelectorFromString([OCMRealMethodAliasPrefix stringByAppendingString:NSStringFromSelector(selector)]); |
| class_addMethod(subclass, aliasSelector, originalImp, method_getTypeEncoding(originalMethod)); |
| } |
| |
| - (void)forwardInvocationForRealObject:(NSInvocation *)anInvocation |
| { |
| // in here "self" is a reference to the real object, not the mock |
| OCPartialMockObject *mock = [OCPartialMockObject existingPartialMockForObject:self]; |
| if([mock handleInvocation:anInvocation] == NO) |
| [NSException raise:NSInternalInconsistencyException format:@"Ended up in subclass forwarder for %@ with unstubbed method %@", |
| [self class], NSStringFromSelector([anInvocation selector])]; |
| } |
| |
| |
| |
| #pragma mark Overrides |
| |
| - (id)getNewRecorder |
| { |
| return [[[OCPartialMockRecorder alloc] initWithSignatureResolver:self] autorelease]; |
| } |
| |
| - (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation |
| { |
| [anInvocation invokeWithTarget:realObject]; |
| } |
| |
| |
| @end |