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 "OCMFunctionsPrivate.h" |
| 21 #import "OCMInvocationStub.h" |
| 22 #import "NSMethodSignature+OCMAdditions.h" |
8 | 23 |
9 @implementation OCClassMockObject | 24 @implementation OCClassMockObject |
10 | 25 |
11 #pragma mark Initialisers, description, accessors, etc. | 26 #pragma mark Initialisers, description, accessors, etc. |
12 | 27 |
13 - (id)initWithClass:(Class)aClass | 28 - (id)initWithClass:(Class)aClass |
14 { | 29 { |
| 30 NSParameterAssert(aClass != nil); |
15 [super init]; | 31 [super init]; |
16 mockedClass = aClass; | 32 mockedClass = aClass; |
| 33 [self prepareClassForClassMethodMocking]; |
17 return self; | 34 return self; |
18 } | 35 } |
19 | 36 |
| 37 - (void)dealloc |
| 38 { |
| 39 [self stopMocking]; |
| 40 [super dealloc]; |
| 41 } |
| 42 |
20 - (NSString *)description | 43 - (NSString *)description |
21 { | 44 { |
22 » return [NSString stringWithFormat:@"OCMockObject[%@]", NSStringFromClass
(mockedClass)]; | 45 » return [NSString stringWithFormat:@"OCMockObject(%@)", NSStringFromClass
(mockedClass)]; |
23 } | 46 } |
24 | 47 |
25 - (Class)mockedClass | 48 - (Class)mockedClass |
26 { | 49 { |
27 return mockedClass; | 50 return mockedClass; |
28 } | 51 } |
29 | 52 |
| 53 #pragma mark Extending/overriding superclass behaviour |
| 54 |
| 55 - (void)stopMocking |
| 56 { |
| 57 if(originalMetaClass != nil) |
| 58 [self restoreMetaClass]; |
| 59 [super stopMocking]; |
| 60 } |
| 61 |
| 62 - (void)restoreMetaClass |
| 63 { |
| 64 OCMSetAssociatedMockForClass(nil, mockedClass); |
| 65 OCMSetIsa(mockedClass, originalMetaClass); |
| 66 originalMetaClass = nil; |
| 67 } |
| 68 |
| 69 - (void)addStub:(OCMInvocationStub *)aStub |
| 70 { |
| 71 [super addStub:aStub]; |
| 72 if([aStub recordedAsClassMethod]) |
| 73 [self setupForwarderForClassMethodSelector:[[aStub recordedInvocation] s
elector]]; |
| 74 } |
| 75 |
| 76 |
| 77 #pragma mark Class method mocking |
| 78 |
| 79 - (void)prepareClassForClassMethodMocking |
| 80 { |
| 81 /* haven't figured out how to work around runtime dependencies on NSString,
so exclude it for now */ |
| 82 if([[mockedClass class] isSubclassOfClass:[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 NSMethodSignature *signature = [mockedClass instanceMethodSignatureForSelect
or:aSelector]; |
| 176 if(signature == nil) |
| 177 { |
| 178 NSString *property = NSStringFromSelector(aSelector); |
| 179 signature = [NSMethodSignature signatureOfGetterForDynamicProperty:prope
rty inClass:mockedClass]; |
| 180 } |
| 181 return signature; |
| 182 } |
| 183 |
| 184 - (Class)mockObjectClass |
| 185 { |
| 186 return [super class]; |
| 187 } |
| 188 |
| 189 - (Class)class |
| 190 { |
| 191 return mockedClass; |
36 } | 192 } |
37 | 193 |
38 - (BOOL)respondsToSelector:(SEL)selector | 194 - (BOOL)respondsToSelector:(SEL)selector |
39 { | 195 { |
40 return [mockedClass instancesRespondToSelector:selector]; | 196 return [mockedClass instancesRespondToSelector:selector]; |
41 } | 197 } |
42 | 198 |
| 199 - (BOOL)isKindOfClass:(Class)aClass |
| 200 { |
| 201 return [mockedClass isSubclassOfClass:aClass]; |
| 202 } |
| 203 |
| 204 - (BOOL)conformsToProtocol:(Protocol *)aProtocol |
| 205 { |
| 206 return class_conformsToProtocol(mockedClass, aProtocol); |
| 207 } |
| 208 |
43 @end | 209 @end |
| 210 |
| 211 |
| 212 #pragma mark - |
| 213 |
| 214 /** |
| 215 taken from: |
| 216 `class-dump -f isNS /Applications/Xcode.app/Contents/Developer/Platforms/iPhone
Simulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Framewor
ks/CoreFoundation.framework` |
| 217 |
| 218 @interface NSObject (__NSIsKinds) |
| 219 - (_Bool)isNSValue__; |
| 220 - (_Bool)isNSTimeZone__; |
| 221 - (_Bool)isNSString__; |
| 222 - (_Bool)isNSSet__; |
| 223 - (_Bool)isNSOrderedSet__; |
| 224 - (_Bool)isNSNumber__; |
| 225 - (_Bool)isNSDictionary__; |
| 226 - (_Bool)isNSDate__; |
| 227 - (_Bool)isNSData__; |
| 228 - (_Bool)isNSArray__; |
| 229 */ |
| 230 |
| 231 @implementation OCClassMockObject(NSIsKindsImplementation) |
| 232 |
| 233 - (BOOL)isNSValue__ |
| 234 { |
| 235 return [mockedClass isSubclassOfClass:[NSValue class]]; |
| 236 } |
| 237 |
| 238 - (BOOL)isNSTimeZone__ |
| 239 { |
| 240 return [mockedClass isSubclassOfClass:[NSTimeZone class]]; |
| 241 } |
| 242 |
| 243 - (BOOL)isNSSet__ |
| 244 { |
| 245 return [mockedClass isSubclassOfClass:[NSSet class]]; |
| 246 } |
| 247 |
| 248 - (BOOL)isNSOrderedSet__ |
| 249 { |
| 250 return [mockedClass isSubclassOfClass:[NSOrderedSet class]]; |
| 251 } |
| 252 |
| 253 - (BOOL)isNSNumber__ |
| 254 { |
| 255 return [mockedClass isSubclassOfClass:[NSNumber class]]; |
| 256 } |
| 257 |
| 258 - (BOOL)isNSDate__ |
| 259 { |
| 260 return [mockedClass isSubclassOfClass:[NSDate class]]; |
| 261 } |
| 262 |
| 263 - (BOOL)isNSString__ |
| 264 { |
| 265 return [mockedClass isSubclassOfClass:[NSString class]]; |
| 266 } |
| 267 |
| 268 - (BOOL)isNSDictionary__ |
| 269 { |
| 270 return [mockedClass isSubclassOfClass:[NSDictionary class]]; |
| 271 } |
| 272 |
| 273 - (BOOL)isNSData__ |
| 274 { |
| 275 return [mockedClass isSubclassOfClass:[NSData class]]; |
| 276 } |
| 277 |
| 278 - (BOOL)isNSArray__ |
| 279 { |
| 280 return [mockedClass isSubclassOfClass:[NSArray class]]; |
| 281 } |
| 282 |
| 283 @end |
OLD | NEW |