OCMockObject.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /*
  2. * Copyright (c) 2004-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 <OCMock/OCMockObject.h>
  17. #import "OCClassMockObject.h"
  18. #import "OCProtocolMockObject.h"
  19. #import "OCPartialMockObject.h"
  20. #import "OCObserverMockObject.h"
  21. #import "OCMStubRecorder.h"
  22. #import <OCMock/OCMLocation.h>
  23. #import "NSInvocation+OCMAdditions.h"
  24. #import "OCMInvocationMatcher.h"
  25. #import "OCMMacroState.h"
  26. #import "OCMFunctions.h"
  27. #import "OCMVerifier.h"
  28. #import "OCMInvocationExpectation.h"
  29. #import "OCMExpectationRecorder.h"
  30. @implementation OCMockObject
  31. #pragma mark Class initialisation
  32. + (void)initialize
  33. {
  34. if([[NSInvocation class] instanceMethodSignatureForSelector:@selector(getArgumentAtIndexAsObject:)] == NULL)
  35. [NSException raise:NSInternalInconsistencyException format:@"** Expected method not present; the method getArgumentAtIndexAsObject: is not implemented by NSInvocation. If you see this exception it is likely that you are using the static library version of OCMock and your project is not configured correctly to load categories from static libraries. Did you forget to add the -ObjC linker flag?"];
  36. }
  37. #pragma mark Factory methods
  38. + (id)mockForClass:(Class)aClass
  39. {
  40. return [[[OCClassMockObject alloc] initWithClass:aClass] autorelease];
  41. }
  42. + (id)mockForProtocol:(Protocol *)aProtocol
  43. {
  44. return [[[OCProtocolMockObject alloc] initWithProtocol:aProtocol] autorelease];
  45. }
  46. + (id)partialMockForObject:(NSObject *)anObject
  47. {
  48. return [[[OCPartialMockObject alloc] initWithObject:anObject] autorelease];
  49. }
  50. + (id)niceMockForClass:(Class)aClass
  51. {
  52. return [self _makeNice:[self mockForClass:aClass]];
  53. }
  54. + (id)niceMockForProtocol:(Protocol *)aProtocol
  55. {
  56. return [self _makeNice:[self mockForProtocol:aProtocol]];
  57. }
  58. + (id)_makeNice:(OCMockObject *)mock
  59. {
  60. mock->isNice = YES;
  61. return mock;
  62. }
  63. + (id)observerMock
  64. {
  65. return [[[OCObserverMockObject alloc] init] autorelease];
  66. }
  67. #pragma mark Initialisers, description, accessors, etc.
  68. - (instancetype)init
  69. {
  70. // no [super init], we're inheriting from NSProxy
  71. expectationOrderMatters = NO;
  72. stubs = [[NSMutableArray alloc] init];
  73. expectations = [[NSMutableArray alloc] init];
  74. exceptions = [[NSMutableArray alloc] init];
  75. invocations = [[NSMutableArray alloc] init];
  76. return self;
  77. }
  78. - (void)dealloc
  79. {
  80. [stubs release];
  81. [expectations release];
  82. [exceptions release];
  83. [invocations release];
  84. [super dealloc];
  85. }
  86. - (NSString *)description
  87. {
  88. return @"OCMockObject";
  89. }
  90. - (void)addStub:(OCMInvocationStub *)aStub
  91. {
  92. [stubs addObject:aStub];
  93. }
  94. - (void)addExpectation:(OCMInvocationExpectation *)anExpectation
  95. {
  96. [expectations addObject:anExpectation];
  97. }
  98. #pragma mark Public API
  99. - (void)setExpectationOrderMatters:(BOOL)flag
  100. {
  101. expectationOrderMatters = flag;
  102. }
  103. - (void)stopMocking
  104. {
  105. // no-op for mock objects that are not class object or partial mocks
  106. }
  107. - (id)stub
  108. {
  109. return [[[OCMStubRecorder alloc] initWithMockObject:self] autorelease];
  110. }
  111. - (id)expect
  112. {
  113. return [[[OCMExpectationRecorder alloc] initWithMockObject:self] autorelease];
  114. }
  115. - (id)reject
  116. {
  117. return [[self expect] never];
  118. }
  119. - (id)verify
  120. {
  121. return [self verifyAtLocation:nil];
  122. }
  123. - (id)verifyAtLocation:(OCMLocation *)location
  124. {
  125. NSMutableArray *unsatisfiedExpectations = [NSMutableArray array];
  126. for(OCMInvocationExpectation *e in expectations)
  127. {
  128. if(![e isSatisfied])
  129. [unsatisfiedExpectations addObject:e];
  130. }
  131. if([unsatisfiedExpectations count] == 1)
  132. {
  133. NSString *description = [NSString stringWithFormat:@"%@: expected method was not invoked: %@",
  134. [self description], [[unsatisfiedExpectations objectAtIndex:0] description]];
  135. OCMReportFailure(location, description);
  136. }
  137. else if([unsatisfiedExpectations count] > 0)
  138. {
  139. NSString *description = [NSString stringWithFormat:@"%@: %@ expected methods were not invoked: %@",
  140. [self description], @([unsatisfiedExpectations count]), [self _stubDescriptions:YES]];
  141. OCMReportFailure(location, description);
  142. }
  143. if([exceptions count] > 0)
  144. {
  145. NSString *description = [NSString stringWithFormat:@"%@: %@ (This is a strict mock failure that was ignored when it actually occured.)",
  146. [self description], [[exceptions objectAtIndex:0] description]];
  147. OCMReportFailure(location, description);
  148. }
  149. return [[[OCMVerifier alloc] initWithMockObject:self] autorelease];
  150. }
  151. - (void)verifyWithDelay:(NSTimeInterval)delay
  152. {
  153. [self verifyWithDelay:delay atLocation:nil];
  154. }
  155. - (void)verifyWithDelay:(NSTimeInterval)delay atLocation:(OCMLocation *)location
  156. {
  157. NSTimeInterval step = 0.01;
  158. while(delay > 0)
  159. {
  160. if([expectations count] == 0)
  161. break;
  162. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:step]];
  163. delay -= step;
  164. step *= 2;
  165. }
  166. [self verifyAtLocation:location];
  167. }
  168. #pragma mark Verify after running
  169. - (void)verifyInvocation:(OCMInvocationMatcher *)matcher
  170. {
  171. [self verifyInvocation:matcher atLocation:nil];
  172. }
  173. - (void)verifyInvocation:(OCMInvocationMatcher *)matcher atLocation:(OCMLocation *)location
  174. {
  175. for(NSInvocation *invocation in invocations)
  176. {
  177. if([matcher matchesInvocation:invocation])
  178. return;
  179. }
  180. NSString *description = [NSString stringWithFormat:@"%@: Method %@ was not invoked.",
  181. [self description], [matcher description]];
  182. OCMReportFailure(location, description);
  183. }
  184. #pragma mark Handling invocations
  185. - (id)forwardingTargetForSelector:(SEL)aSelector
  186. {
  187. if([OCMMacroState globalState] != nil)
  188. {
  189. OCMRecorder *recorder = [[OCMMacroState globalState] recorder];
  190. [recorder setMockObject:self];
  191. return recorder;
  192. }
  193. return nil;
  194. }
  195. - (BOOL)handleSelector:(SEL)sel
  196. {
  197. for(OCMInvocationStub *recorder in stubs)
  198. if([recorder matchesSelector:sel])
  199. return YES;
  200. return NO;
  201. }
  202. - (void)forwardInvocation:(NSInvocation *)anInvocation
  203. {
  204. @try
  205. {
  206. if([self handleInvocation:anInvocation] == NO)
  207. [self handleUnRecordedInvocation:anInvocation];
  208. }
  209. @catch(NSException *e)
  210. {
  211. [exceptions addObject:e];
  212. [e raise];
  213. }
  214. }
  215. - (BOOL)handleInvocation:(NSInvocation *)anInvocation
  216. {
  217. [invocations addObject:anInvocation];
  218. OCMInvocationStub *stub = nil;
  219. for(stub in stubs)
  220. {
  221. // If the stub forwards its invocation to the real object, then we don't want to do handleInvocation: yet, since forwarding the invocation to the real object could call a method that is expected to happen after this one, which is bad if expectationOrderMatters is YES
  222. if([stub matchesInvocation:anInvocation])
  223. break;
  224. }
  225. // Retain the stub in case it ends up being removed from stubs and expectations, since we still have to call handleInvocation on the stub at the end
  226. [stub retain];
  227. if(stub == nil)
  228. return NO;
  229. if([expectations containsObject:stub])
  230. {
  231. OCMInvocationExpectation *expectation = [self _nextExptectedInvocation];
  232. if(expectationOrderMatters && (expectation != stub))
  233. {
  234. [NSException raise:NSInternalInconsistencyException format:@"%@: unexpected method invoked: %@\n\texpected:\t%@",
  235. [self description], [stub description], [[expectations objectAtIndex:0] description]];
  236. }
  237. // We can't check isSatisfied yet, since the stub won't be satisfied until we call handleInvocation:, and we don't want to call handleInvocation: yes for the reason in the comment above, since we'll still have the current expectation in the expectations array, which will cause an exception if expectationOrderMatters is YES and we're not ready for any future expected methods to be called yet
  238. if(![(OCMInvocationExpectation *)stub isMatchAndReject])
  239. {
  240. [expectations removeObject:stub];
  241. [stubs removeObject:stub];
  242. }
  243. }
  244. [stub handleInvocation:anInvocation];
  245. [stub release];
  246. return YES;
  247. }
  248. - (OCMInvocationExpectation *)_nextExptectedInvocation
  249. {
  250. for(OCMInvocationExpectation *expectation in expectations)
  251. if(![expectation isMatchAndReject])
  252. return expectation;
  253. return nil;
  254. }
  255. - (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation
  256. {
  257. if(isNice == NO)
  258. {
  259. [NSException raise:NSInternalInconsistencyException format:@"%@: unexpected method invoked: %@ %@",
  260. [self description], [anInvocation invocationDescription], [self _stubDescriptions:NO]];
  261. }
  262. }
  263. - (void)doesNotRecognizeSelector:(SEL)aSelector __unused
  264. {
  265. if([OCMMacroState globalState] != nil)
  266. {
  267. // we can't do anything clever with the macro state because we must raise an exception here
  268. [NSException raise:NSInvalidArgumentException format:@"%@: Cannot stub/expect/verify method '%@' because no such method exists in the mocked class.",
  269. [self description], NSStringFromSelector(aSelector)];
  270. }
  271. else
  272. {
  273. [NSException raise:NSInvalidArgumentException format:@"-[%@ %@]: unrecognized selector sent to instance %p",
  274. [self description], NSStringFromSelector(aSelector), (void *)self];
  275. }
  276. }
  277. #pragma mark Helper methods
  278. - (NSString *)_stubDescriptions:(BOOL)onlyExpectations
  279. {
  280. NSMutableString *outputString = [NSMutableString string];
  281. for(OCMStubRecorder *stub in stubs)
  282. {
  283. NSString *prefix = @"";
  284. if(onlyExpectations)
  285. {
  286. if([expectations containsObject:stub] == NO)
  287. continue;
  288. }
  289. else
  290. {
  291. if([expectations containsObject:stub])
  292. prefix = @"expected:\t";
  293. else
  294. prefix = @"stubbed:\t";
  295. }
  296. [outputString appendFormat:@"\n\t%@%@", prefix, [stub description]];
  297. }
  298. return outputString;
  299. }
  300. @end