OLD | NEW |
1 //------------------------------------------------------------------------------
--------- | 1 /* |
2 // $Id$ | 2 * Copyright (c) 2005-2015 Erik Doernenburg and contributors |
3 // Copyright (c) 2005-2008 by Mulle Kybernetik. See License file for details. | 3 * |
4 //------------------------------------------------------------------------------
--------- | 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 5 * not use these files except in compliance with the License. You may obtain |
| 6 * a copy of the License at |
| 7 * |
| 8 * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 * |
| 10 * Unless required by applicable law or agreed to in writing, software |
| 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 13 * License for the specific language governing permissions and limitations |
| 14 * under the License. |
| 15 */ |
5 | 16 |
| 17 #import <objc/runtime.h> |
6 #import "OCClassMockObject.h" | 18 #import "OCClassMockObject.h" |
7 | 19 #import "NSObject+OCMAdditions.h" |
| 20 #import "OCMFunctions.h" |
| 21 #import "OCMInvocationStub.h" |
8 | 22 |
9 @implementation OCClassMockObject | 23 @implementation OCClassMockObject |
10 | 24 |
11 #pragma mark Initialisers, description, accessors, etc. | 25 #pragma mark Initialisers, description, accessors, etc. |
12 | 26 |
13 - (id)initWithClass:(Class)aClass | 27 - (id)initWithClass:(Class)aClass |
14 { | 28 { |
| 29 NSParameterAssert(aClass != nil); |
15 [super init]; | 30 [super init]; |
16 mockedClass = aClass; | 31 mockedClass = aClass; |
| 32 [self prepareClassForClassMethodMocking]; |
17 return self; | 33 return self; |
18 } | 34 } |
19 | 35 |
| 36 - (void)dealloc |
| 37 { |
| 38 [self stopMocking]; |
| 39 [super dealloc]; |
| 40 } |
| 41 |
20 - (NSString *)description | 42 - (NSString *)description |
21 { | 43 { |
22 » return [NSString stringWithFormat:@"OCMockObject[%@]", NSStringFromClass
(mockedClass)]; | 44 » return [NSString stringWithFormat:@"OCMockObject(%@)", NSStringFromClass
(mockedClass)]; |
23 } | 45 } |
24 | 46 |
25 - (Class)mockedClass | 47 - (Class)mockedClass |
26 { | 48 { |
27 return mockedClass; | 49 return mockedClass; |
28 } | 50 } |
29 | 51 |
| 52 #pragma mark Extending/overriding superclass behaviour |
| 53 |
| 54 - (void)stopMocking |
| 55 { |
| 56 if(originalMetaClass != nil) |
| 57 [self restoreMetaClass]; |
| 58 [super stopMocking]; |
| 59 } |
| 60 |
| 61 - (void)restoreMetaClass |
| 62 { |
| 63 OCMSetAssociatedMockForClass(nil, mockedClass); |
| 64 OCMSetIsa(mockedClass, originalMetaClass); |
| 65 originalMetaClass = nil; |
| 66 } |
| 67 |
| 68 - (void)addStub:(OCMInvocationStub *)aStub |
| 69 { |
| 70 [super addStub:aStub]; |
| 71 if([aStub recordedAsClassMethod]) |
| 72 [self setupForwarderForClassMethodSelector:[[aStub recordedInvocation] s
elector]]; |
| 73 } |
| 74 |
| 75 |
| 76 #pragma mark Class method mocking |
| 77 |
| 78 - (void)prepareClassForClassMethodMocking |
| 79 { |
| 80 /* haven't figured out how to work around runtime dependencies on NSString,
so exclude it for now */ |
| 81 /* also weird: [[NSString class] isKindOfClass:[NSString class]] is false, h
ence the additional clause */ |
| 82 if([[mockedClass class] isKindOfClass:[NSString class]] || (mockedClass == [
NSString class])) |
| 83 return; |
| 84 |
| 85 /* if there is another mock for this exact class, stop it */ |
| 86 id otherMock = OCMGetAssociatedMockForClass(mockedClass, NO); |
| 87 if(otherMock != nil) |
| 88 [otherMock restoreMetaClass]; |
| 89 |
| 90 OCMSetAssociatedMockForClass(self, mockedClass); |
| 91 |
| 92 /* dynamically create a subclass and use its meta class as the meta class fo
r the mocked class */ |
| 93 Class subclass = OCMCreateSubclass(mockedClass, mockedClass); |
| 94 originalMetaClass = object_getClass(mockedClass); |
| 95 id newMetaClass = object_getClass(subclass); |
| 96 OCMSetIsa(mockedClass, OCMGetIsa(subclass)); |
| 97 |
| 98 /* point forwardInvocation: of the object to the implementation in the mock
*/ |
| 99 Method myForwardMethod = class_getInstanceMethod([self mockObjectClass], @se
lector(forwardInvocationForClassObject:)); |
| 100 IMP myForwardIMP = method_getImplementation(myForwardMethod); |
| 101 class_addMethod(newMetaClass, @selector(forwardInvocation:), myForwardIMP, m
ethod_getTypeEncoding(myForwardMethod)); |
| 102 |
| 103 /* create a dummy initialize method */ |
| 104 Method myDummyInitializeMethod = class_getInstanceMethod([self mockObjectCla
ss], @selector(initializeForClassObject)); |
| 105 const char *initializeTypes = method_getTypeEncoding(myDummyInitializeMethod
); |
| 106 IMP myDummyInitializeIMP = method_getImplementation(myDummyInitializeMethod)
; |
| 107 class_addMethod(newMetaClass, @selector(initialize), myDummyInitializeIMP, i
nitializeTypes); |
| 108 |
| 109 /* adding forwarder for most class methods (instance methods on meta class)
to allow for verify after run */ |
| 110 NSArray *methodBlackList = @[@"class", @"forwardingTargetForSelector:", @"me
thodSignatureForSelector:", @"forwardInvocation:", @"isBlock", |
| 111 @"instanceMethodForwarderForSelector:", @"instanceMethodSignatureFor
Selector:"]; |
| 112 [NSObject enumerateMethodsInClass:originalMetaClass usingBlock:^(Class cls,
SEL sel) { |
| 113 if((cls == object_getClass([NSObject class])) || (cls == [NSObject class
]) || (cls == object_getClass(cls))) |
| 114 return; |
| 115 NSString *className = NSStringFromClass(cls); |
| 116 NSString *selName = NSStringFromSelector(sel); |
| 117 if(([className hasPrefix:@"NS"] || [className hasPrefix:@"UI"]) && |
| 118 ([selName hasPrefix:@"_"] || [selName hasSuffix:@"_"])) |
| 119 return; |
| 120 if([methodBlackList containsObject:selName]) |
| 121 return; |
| 122 @try |
| 123 { |
| 124 [self setupForwarderForClassMethodSelector:sel]; |
| 125 } |
| 126 @catch(NSException *e) |
| 127 { |
| 128 // ignore for now |
| 129 } |
| 130 }]; |
| 131 } |
| 132 |
| 133 - (void)setupForwarderForClassMethodSelector:(SEL)selector |
| 134 { |
| 135 SEL aliasSelector = OCMAliasForOriginalSelector(selector); |
| 136 if(class_getClassMethod(mockedClass, aliasSelector) != NULL) |
| 137 return; |
| 138 |
| 139 Method originalMethod = class_getClassMethod(mockedClass, selector); |
| 140 IMP originalIMP = method_getImplementation(originalMethod); |
| 141 const char *types = method_getTypeEncoding(originalMethod); |
| 142 |
| 143 Class metaClass = object_getClass(mockedClass); |
| 144 IMP forwarderIMP = [originalMetaClass instanceMethodForwarderForSelector:sel
ector]; |
| 145 class_replaceMethod(metaClass, selector, forwarderIMP, types); |
| 146 class_addMethod(metaClass, aliasSelector, originalIMP, types); |
| 147 } |
| 148 |
| 149 |
| 150 - (void)forwardInvocationForClassObject:(NSInvocation *)anInvocation |
| 151 { |
| 152 // in here "self" is a reference to the real class, not the mock |
| 153 OCClassMockObject *mock = OCMGetAssociatedMockForClass((Class) self, YES
); |
| 154 if(mock == nil) |
| 155 { |
| 156 [NSException raise:NSInternalInconsistencyException format:@"No mock for
class %@", NSStringFromClass((Class)self)]; |
| 157 } |
| 158 if([mock handleInvocation:anInvocation] == NO) |
| 159 { |
| 160 [anInvocation setSelector:OCMAliasForOriginalSelector([anInvocation sele
ctor])]; |
| 161 [anInvocation invoke]; |
| 162 } |
| 163 } |
| 164 |
| 165 - (void)initializeForClassObject |
| 166 { |
| 167 // we really just want to have an implementation so that the superclass's is
not called |
| 168 } |
| 169 |
30 | 170 |
31 #pragma mark Proxy API | 171 #pragma mark Proxy API |
32 | 172 |
33 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector | 173 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector |
34 { | 174 { |
35 » return [mockedClass instanceMethodSignatureForSelector:aSelector]; | 175 return [mockedClass instanceMethodSignatureForSelector:aSelector]; |
| 176 } |
| 177 |
| 178 - (Class)mockObjectClass |
| 179 { |
| 180 return [super class]; |
| 181 } |
| 182 |
| 183 - (Class)class |
| 184 { |
| 185 return mockedClass; |
36 } | 186 } |
37 | 187 |
38 - (BOOL)respondsToSelector:(SEL)selector | 188 - (BOOL)respondsToSelector:(SEL)selector |
39 { | 189 { |
40 return [mockedClass instancesRespondToSelector:selector]; | 190 return [mockedClass instancesRespondToSelector:selector]; |
41 } | 191 } |
42 | 192 |
| 193 - (BOOL)isKindOfClass:(Class)aClass |
| 194 { |
| 195 return [mockedClass isSubclassOfClass:aClass]; |
| 196 } |
| 197 |
| 198 - (BOOL)conformsToProtocol:(Protocol *)aProtocol |
| 199 { |
| 200 return class_conformsToProtocol(mockedClass, aProtocol); |
| 201 } |
| 202 |
43 @end | 203 @end |
| 204 |
| 205 |
| 206 #pragma mark - |
| 207 |
| 208 /** |
| 209 taken from: |
| 210 `class-dump -f isNS /Applications/Xcode.app/Contents/Developer/Platforms/iPhone
Simulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Framewor
ks/CoreFoundation.framework` |
| 211 |
| 212 @interface NSObject (__NSIsKinds) |
| 213 - (_Bool)isNSValue__; |
| 214 - (_Bool)isNSTimeZone__; |
| 215 - (_Bool)isNSString__; |
| 216 - (_Bool)isNSSet__; |
| 217 - (_Bool)isNSOrderedSet__; |
| 218 - (_Bool)isNSNumber__; |
| 219 - (_Bool)isNSDictionary__; |
| 220 - (_Bool)isNSDate__; |
| 221 - (_Bool)isNSData__; |
| 222 - (_Bool)isNSArray__; |
| 223 */ |
| 224 |
| 225 @implementation OCClassMockObject(NSIsKindsImplementation) |
| 226 |
| 227 - (BOOL)isNSValue__ |
| 228 { |
| 229 return [mockedClass isKindOfClass:[NSValue class]]; |
| 230 } |
| 231 |
| 232 - (BOOL)isNSTimeZone__ |
| 233 { |
| 234 return [mockedClass isKindOfClass:[NSTimeZone class]]; |
| 235 } |
| 236 |
| 237 - (BOOL)isNSSet__ |
| 238 { |
| 239 return [mockedClass isKindOfClass:[NSSet class]]; |
| 240 } |
| 241 |
| 242 - (BOOL)isNSOrderedSet__ |
| 243 { |
| 244 return [mockedClass isKindOfClass:[NSOrderedSet class]]; |
| 245 } |
| 246 |
| 247 - (BOOL)isNSNumber__ |
| 248 { |
| 249 return [mockedClass isKindOfClass:[NSNumber class]]; |
| 250 } |
| 251 |
| 252 - (BOOL)isNSDate__ |
| 253 { |
| 254 return [mockedClass isKindOfClass:[NSDate class]]; |
| 255 } |
| 256 |
| 257 - (BOOL)isNSString__ |
| 258 { |
| 259 return [mockedClass isKindOfClass:[NSString class]]; |
| 260 } |
| 261 |
| 262 - (BOOL)isNSDictionary__ |
| 263 { |
| 264 return [mockedClass isKindOfClass:[NSDictionary class]]; |
| 265 } |
| 266 |
| 267 - (BOOL)isNSData__ |
| 268 { |
| 269 return [mockedClass isKindOfClass:[NSData class]]; |
| 270 } |
| 271 |
| 272 - (BOOL)isNSArray__ |
| 273 { |
| 274 return [mockedClass isKindOfClass:[NSArray class]]; |
| 275 } |
| 276 |
| 277 @end |
OLD | NEW |