OCClassMockObject.m 7.9 KB

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