1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
//---------------------------------------------------------------------------------------
// $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
|