Home | History | Annotate | Download | only in OCMock
      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