blob: fb0b8bd592bc6298b58e8a7491860d509d622c41 [file] [log] [blame]
//---------------------------------------------------------------------------------------
// $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