| 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 { |
| 15 » [super init]; | 29 NSParameterAssert(aClass != nil); |
| 16 » mockedClass = aClass; | 30 [super init]; |
| 17 » return self; | 31 mockedClass = aClass; |
| 32 [self prepareClassForClassMethodMocking]; |
| 33 return self; |
| 34 } |
| 35 |
| 36 - (void)dealloc { |
| 37 [self stopMocking]; |
| 38 [super dealloc]; |
| 18 } | 39 } |
| 19 | 40 |
| 20 - (NSString *)description | 41 - (NSString *)description |
| 21 { | 42 { |
| 22 » return [NSString stringWithFormat:@"OCMockObject[%@]", NSStringFromClass
(mockedClass)]; | 43 return [NSString |
| 44 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 if (originalMetaClass != nil) |
| 56 [self restoreMetaClass]; |
| 57 [super stopMocking]; |
| 58 } |
| 59 |
| 60 - (void)restoreMetaClass { |
| 61 OCMSetAssociatedMockForClass(nil, mockedClass); |
| 62 OCMSetIsa(mockedClass, originalMetaClass); |
| 63 originalMetaClass = nil; |
| 64 } |
| 65 |
| 66 - (void)addStub:(OCMInvocationStub*)aStub { |
| 67 [super addStub:aStub]; |
| 68 if ([aStub recordedAsClassMethod]) |
| 69 [self setupForwarderForClassMethodSelector:[[aStub recordedInvocation] |
| 70 selector]]; |
| 71 } |
| 72 |
| 73 #pragma mark Class method mocking |
| 74 |
| 75 - (void)prepareClassForClassMethodMocking { |
| 76 /* haven't figured out how to work around runtime dependencies on NSString, so |
| 77 * exclude it for now */ |
| 78 /* also weird: [[NSString class] isKindOfClass:[NSString class]] is false, |
| 79 * hence the additional clause */ |
| 80 if ([[mockedClass class] isKindOfClass:[NSString class]] || |
| 81 (mockedClass == [NSString class])) |
| 82 return; |
| 83 |
| 84 /* if there is another mock for this exact class, stop it */ |
| 85 id otherMock = OCMGetAssociatedMockForClass(mockedClass, NO); |
| 86 if (otherMock != nil) |
| 87 [otherMock restoreMetaClass]; |
| 88 |
| 89 OCMSetAssociatedMockForClass(self, mockedClass); |
| 90 |
| 91 /* dynamically create a subclass and use its meta class as the meta class for |
| 92 * 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( |
| 100 [self mockObjectClass], @selector(forwardInvocationForClassObject:)); |
| 101 IMP myForwardIMP = method_getImplementation(myForwardMethod); |
| 102 class_addMethod(newMetaClass, @selector(forwardInvocation:), myForwardIMP, |
| 103 method_getTypeEncoding(myForwardMethod)); |
| 104 |
| 105 /* create a dummy initialize method */ |
| 106 Method myDummyInitializeMethod = class_getInstanceMethod( |
| 107 [self mockObjectClass], @selector(initializeForClassObject)); |
| 108 const char* initializeTypes = method_getTypeEncoding(myDummyInitializeMethod); |
| 109 IMP myDummyInitializeIMP = method_getImplementation(myDummyInitializeMethod); |
| 110 class_addMethod(newMetaClass, @selector(initialize), myDummyInitializeIMP, |
| 111 initializeTypes); |
| 112 |
| 113 /* adding forwarder for most class methods (instance methods on meta class) to |
| 114 * allow for verify after run */ |
| 115 NSArray* methodBlackList = @[ |
| 116 @"class", @"forwardingTargetForSelector:", @"methodSignatureForSelector:", |
| 117 @"forwardInvocation:", @"isBlock", @"instanceMethodForwarderForSelector:", |
| 118 @"instanceMethodSignatureForSelector:" |
| 119 ]; |
| 120 [NSObject |
| 121 enumerateMethodsInClass:originalMetaClass |
| 122 usingBlock:^(Class cls, SEL sel) { |
| 123 if ((cls == object_getClass([NSObject class])) || |
| 124 (cls == [NSObject class]) || |
| 125 (cls == object_getClass(cls))) |
| 126 return; |
| 127 NSString* className = NSStringFromClass(cls); |
| 128 NSString* selName = NSStringFromSelector(sel); |
| 129 if (([className hasPrefix:@"NS"] || |
| 130 [className hasPrefix:@"UI"]) && |
| 131 ([selName hasPrefix:@"_"] || [selName hasSuffix:@"_"])) |
| 132 return; |
| 133 if ([methodBlackList containsObject:selName]) |
| 134 return; |
| 135 @try { |
| 136 [self setupForwarderForClassMethodSelector:sel]; |
| 137 } @catch (NSException* e) { |
| 138 // ignore for now |
| 139 } |
| 140 }]; |
| 141 } |
| 142 |
| 143 - (void)setupForwarderForClassMethodSelector:(SEL)selector { |
| 144 SEL aliasSelector = OCMAliasForOriginalSelector(selector); |
| 145 if (class_getClassMethod(mockedClass, aliasSelector) != NULL) |
| 146 return; |
| 147 |
| 148 Method originalMethod = class_getClassMethod(mockedClass, selector); |
| 149 IMP originalIMP = method_getImplementation(originalMethod); |
| 150 const char* types = method_getTypeEncoding(originalMethod); |
| 151 |
| 152 Class metaClass = object_getClass(mockedClass); |
| 153 IMP forwarderIMP = |
| 154 [originalMetaClass instanceMethodForwarderForSelector:selector]; |
| 155 class_replaceMethod(metaClass, selector, forwarderIMP, types); |
| 156 class_addMethod(metaClass, aliasSelector, originalIMP, types); |
| 157 } |
| 158 |
| 159 - (void)forwardInvocationForClassObject:(NSInvocation*)anInvocation { |
| 160 // in here "self" is a reference to the real class, not the mock |
| 161 OCClassMockObject* mock = OCMGetAssociatedMockForClass((Class)self, YES); |
| 162 if (mock == nil) { |
| 163 [NSException raise:NSInternalInconsistencyException |
| 164 format:@"No mock for class %@", NSStringFromClass((Class)self)]; |
| 165 } |
| 166 if ([mock handleInvocation:anInvocation] == NO) { |
| 167 [anInvocation |
| 168 setSelector:OCMAliasForOriginalSelector([anInvocation selector])]; |
| 169 [anInvocation invoke]; |
| 170 } |
| 171 } |
| 172 |
| 173 - (void)initializeForClassObject { |
| 174 // we really just want to have an implementation so that the superclass's is |
| 175 // not called |
| 176 } |
| 30 | 177 |
| 31 #pragma mark Proxy API | 178 #pragma mark Proxy API |
| 32 | 179 |
| 33 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector | 180 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector |
| 34 { | 181 { |
| 35 » return [mockedClass instanceMethodSignatureForSelector:aSelector]; | 182 return [mockedClass instanceMethodSignatureForSelector:aSelector]; |
| 183 } |
| 184 |
| 185 - (Class)mockObjectClass { |
| 186 return [super class]; |
| 187 } |
| 188 |
| 189 - (Class) class { |
| 190 return mockedClass; |
| 36 } | 191 } |
| 37 | 192 |
| 38 - (BOOL)respondsToSelector:(SEL)selector | 193 - (BOOL)respondsToSelector:(SEL)selector |
| 39 { | 194 { |
| 40 return [mockedClass instancesRespondToSelector:selector]; | 195 return [mockedClass instancesRespondToSelector:selector]; |
| 41 } | 196 } |
| 42 | 197 |
| 198 - (BOOL)isKindOfClass:(Class)aClass { |
| 199 return [mockedClass isSubclassOfClass:aClass]; |
| 200 } |
| 201 |
| 202 - (BOOL)conformsToProtocol:(Protocol*)aProtocol { |
| 203 return class_conformsToProtocol(mockedClass, aProtocol); |
| 204 } |
| 205 |
| 43 @end | 206 @end |
| 207 |
| 208 #pragma mark - |
| 209 |
| 210 /** |
| 211 taken from: |
| 212 `class-dump -f isNS |
| 213 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/D
eveloper/SDKs/iPhoneSimulator7.0.sdk/System/Library/Frameworks/CoreFoundation.fr
amework` |
| 214 |
| 215 @interface NSObject (__NSIsKinds) |
| 216 - (_Bool)isNSValue__; |
| 217 - (_Bool)isNSTimeZone__; |
| 218 - (_Bool)isNSString__; |
| 219 - (_Bool)isNSSet__; |
| 220 - (_Bool)isNSOrderedSet__; |
| 221 - (_Bool)isNSNumber__; |
| 222 - (_Bool)isNSDictionary__; |
| 223 - (_Bool)isNSDate__; |
| 224 - (_Bool)isNSData__; |
| 225 - (_Bool)isNSArray__; |
| 226 */ |
| 227 |
| 228 @implementation OCClassMockObject (NSIsKindsImplementation) |
| 229 |
| 230 - (BOOL)isNSValue__ { |
| 231 return [mockedClass isKindOfClass:[NSValue class]]; |
| 232 } |
| 233 |
| 234 - (BOOL)isNSTimeZone__ { |
| 235 return [mockedClass isKindOfClass:[NSTimeZone class]]; |
| 236 } |
| 237 |
| 238 - (BOOL)isNSSet__ { |
| 239 return [mockedClass isKindOfClass:[NSSet class]]; |
| 240 } |
| 241 |
| 242 - (BOOL)isNSOrderedSet__ { |
| 243 return [mockedClass isKindOfClass:[NSOrderedSet class]]; |
| 244 } |
| 245 |
| 246 - (BOOL)isNSNumber__ { |
| 247 return [mockedClass isKindOfClass:[NSNumber class]]; |
| 248 } |
| 249 |
| 250 - (BOOL)isNSDate__ { |
| 251 return [mockedClass isKindOfClass:[NSDate class]]; |
| 252 } |
| 253 |
| 254 - (BOOL)isNSString__ { |
| 255 return [mockedClass isKindOfClass:[NSString class]]; |
| 256 } |
| 257 |
| 258 - (BOOL)isNSDictionary__ { |
| 259 return [mockedClass isKindOfClass:[NSDictionary class]]; |
| 260 } |
| 261 |
| 262 - (BOOL)isNSData__ { |
| 263 return [mockedClass isKindOfClass:[NSData class]]; |
| 264 } |
| 265 |
| 266 - (BOOL)isNSArray__ { |
| 267 return [mockedClass isKindOfClass:[NSArray class]]; |
| 268 } |
| 269 |
| 270 @end |
| OLD | NEW |