• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
16NSString *OCMRealMethodAliasPrefix = @"ocmock_replaced_";
17
18@implementation OCPartialMockObject
19
20
21#pragma mark  Mock table
22
23static 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