| OLD | NEW |
| 1 //------------------------------------------------------------------------------
--------- | 1 /* |
| 2 // $Id$ | 2 * Copyright (c) 2004-2015 Erik Doernenburg and contributors |
| 3 // Copyright (c) 2004-2009 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 |
| 6 #import <OCMock/OCMockObject.h> | 17 #import <OCMock/OCMockObject.h> |
| 7 #import "OCClassMockObject.h" | 18 #import "OCClassMockObject.h" |
| 8 #import "OCProtocolMockObject.h" | 19 #import "OCProtocolMockObject.h" |
| 9 #import "OCPartialMockObject.h" | 20 #import "OCPartialMockObject.h" |
| 10 #import "OCObserverMockObject.h" | 21 #import "OCObserverMockObject.h" |
| 11 #import <OCMock/OCMockRecorder.h> | 22 #import "OCMStubRecorder.h" |
| 23 #import <OCMock/OCMLocation.h> |
| 12 #import "NSInvocation+OCMAdditions.h" | 24 #import "NSInvocation+OCMAdditions.h" |
| 13 | 25 #import "OCMInvocationMatcher.h" |
| 14 @interface OCMockObject(Private) | 26 #import "OCMMacroState.h" |
| 15 + (id)_makeNice:(OCMockObject *)mock; | 27 #import "OCMFunctions.h" |
| 16 - (NSString *)_recorderDescriptions:(BOOL)onlyExpectations; | 28 #import "OCMVerifier.h" |
| 17 @end | 29 #import "OCMInvocationExpectation.h" |
| 18 | 30 #import "OCMExpectationRecorder.h" |
| 19 #pragma mark - | |
| 20 | |
| 21 | 31 |
| 22 @implementation OCMockObject | 32 @implementation OCMockObject |
| 23 | 33 |
| 24 #pragma mark Class initialisation | 34 #pragma mark Class initialisation |
| 25 | 35 |
| 26 + (void)initialize | 36 + (void)initialize |
| 27 { | 37 { |
| 28 if([[NSInvocation class] instanceMethodSignatureForSelector:@selector(ge
tArgumentAtIndexAsObject:)] == NULL) | 38 if([[NSInvocation class] instanceMethodSignatureForSelector:@selector(ge
tArgumentAtIndexAsObject:)] == NULL) |
| 29 » » [NSException raise:NSInternalInconsistencyException format:@"**
Expected method not present; the method getArgumentAtIndexAsObject: is not imple
mented by NSInvocation. If you see this exception it is likely that you are usin
g the static library version of OCMock and your project is not configured correc
tly to load categories from static libraries. Did you forget to add the -force_l
oad linker flag?"]; | 39 [NSException |
| 40 raise:NSInternalInconsistencyException |
| 41 format:@"** Expected method not present; the method " |
| 42 @"getArgumentAtIndexAsObject: is not implemented by " |
| 43 @"NSInvocation. If you see this exception it is likely " |
| 44 @"that you are using the static library version of " |
| 45 @"OCMock and your project is not configured correctly to " |
| 46 @"load categories from static libraries. Did you forget " |
| 47 @"to add the -ObjC linker flag?"]; |
| 30 } | 48 } |
| 31 | 49 |
| 32 | 50 |
| 33 #pragma mark Factory methods | 51 #pragma mark Factory methods |
| 34 | 52 |
| 35 + (id)mockForClass:(Class)aClass | 53 + (id)mockForClass:(Class)aClass |
| 36 { | 54 { |
| 37 return [[[OCClassMockObject alloc] initWithClass:aClass] autorelease]; | 55 return [[[OCClassMockObject alloc] initWithClass:aClass] autorelease]; |
| 38 } | 56 } |
| 39 | 57 |
| (...skipping 25 matching lines...) Expand all Loading... |
| 65 return mock; | 83 return mock; |
| 66 } | 84 } |
| 67 | 85 |
| 68 | 86 |
| 69 + (id)observerMock | 87 + (id)observerMock |
| 70 { | 88 { |
| 71 return [[[OCObserverMockObject alloc] init] autorelease]; | 89 return [[[OCObserverMockObject alloc] init] autorelease]; |
| 72 } | 90 } |
| 73 | 91 |
| 74 | 92 |
| 75 | |
| 76 #pragma mark Initialisers, description, accessors, etc. | 93 #pragma mark Initialisers, description, accessors, etc. |
| 77 | 94 |
| 78 - (id)init | 95 - (instancetype)init { |
| 79 { | 96 // no [super init], we're inheriting from NSProxy |
| 80 » // no [super init], we're inheriting from NSProxy | 97 expectationOrderMatters = NO; |
| 81 » expectationOrderMatters = NO; | 98 stubs = [[NSMutableArray alloc] init]; |
| 82 » recorders = [[NSMutableArray alloc] init]; | 99 expectations = [[NSMutableArray alloc] init]; |
| 83 » expectations = [[NSMutableArray alloc] init]; | 100 exceptions = [[NSMutableArray alloc] init]; |
| 84 » rejections = [[NSMutableArray alloc] init]; | 101 invocations = [[NSMutableArray alloc] init]; |
| 85 » exceptions = [[NSMutableArray alloc] init]; | 102 return self; |
| 86 » return self; | |
| 87 } | 103 } |
| 88 | 104 |
| 89 - (void)dealloc | 105 - (void)dealloc |
| 90 { | 106 { |
| 91 » [recorders release]; | 107 [stubs release]; |
| 92 » [expectations release]; | 108 [expectations release]; |
| 93 » [rejections» release]; | 109 [exceptions release]; |
| 94 » [exceptions release]; | 110 [invocations release]; |
| 95 » [super dealloc]; | 111 [super dealloc]; |
| 96 } | 112 } |
| 97 | 113 |
| 98 - (NSString *)description | 114 - (NSString *)description |
| 99 { | 115 { |
| 100 return @"OCMockObject"; | 116 return @"OCMockObject"; |
| 101 } | 117 } |
| 102 | 118 |
| 119 - (void)addStub:(OCMInvocationStub*)aStub { |
| 120 [stubs addObject:aStub]; |
| 121 } |
| 122 |
| 123 - (void)addExpectation:(OCMInvocationExpectation*)anExpectation { |
| 124 [expectations addObject:anExpectation]; |
| 125 } |
| 126 |
| 127 #pragma mark Public API |
| 103 | 128 |
| 104 - (void)setExpectationOrderMatters:(BOOL)flag | 129 - (void)setExpectationOrderMatters:(BOOL)flag |
| 105 { | 130 { |
| 106 expectationOrderMatters = flag; | 131 expectationOrderMatters = flag; |
| 107 } | 132 } |
| 108 | 133 |
| 109 | 134 - (void)stopMocking { |
| 110 #pragma mark Public API | 135 // no-op for mock objects that are not class object or partial mocks |
| 136 } |
| 111 | 137 |
| 112 - (id)stub | 138 - (id)stub |
| 113 { | 139 { |
| 114 » OCMockRecorder *recorder = [self getNewRecorder]; | 140 return [[[OCMStubRecorder alloc] initWithMockObject:self] autorelease]; |
| 115 » [recorders addObject:recorder]; | 141 } |
| 116 » return recorder; | |
| 117 } | |
| 118 | |
| 119 | 142 |
| 120 - (id)expect | 143 - (id)expect |
| 121 { | 144 { |
| 122 » OCMockRecorder *recorder = [self stub]; | 145 return [[[OCMExpectationRecorder alloc] initWithMockObject:self] autorelease]; |
| 123 » [expectations addObject:recorder]; | 146 } |
| 124 » return recorder; | |
| 125 } | |
| 126 | |
| 127 | 147 |
| 128 - (id)reject | 148 - (id)reject |
| 129 { | 149 { |
| 130 » OCMockRecorder *recorder = [self stub]; | 150 return [[self expect] never]; |
| 131 » [rejections addObject:recorder]; | 151 } |
| 132 » return recorder; | 152 |
| 133 } | 153 - (id)verify { |
| 134 | 154 return [self verifyAtLocation:nil]; |
| 135 | 155 } |
| 136 - (void)verify | 156 |
| 137 { | 157 - (id)verifyAtLocation:(OCMLocation*)location { |
| 138 » if([expectations count] == 1) | 158 NSMutableArray* unsatisfiedExpectations = [NSMutableArray array]; |
| 139 » { | 159 for (OCMInvocationExpectation* e in expectations) { |
| 140 » » [NSException raise:NSInternalInconsistencyException format:@"%@:
expected method was not invoked: %@", | 160 if (![e isSatisfied]) |
| 141 » » » [self description], [[expectations objectAtIndex:0] desc
ription]]; | 161 [unsatisfiedExpectations addObject:e]; |
| 142 » } | 162 } |
| 143 » if([expectations count] > 0) | 163 |
| 144 » { | 164 if ([unsatisfiedExpectations count] == 1) { |
| 145 » » [NSException raise:NSInternalInconsistencyException format:@"%@
: %ld expected methods were not invoked: %@", | 165 NSString* description = [NSString |
| 146 » » » [self description], (unsigned long)[expectations count],
[self _recorderDescriptions:YES]]; | 166 stringWithFormat:@"%@: expected method was not invoked: %@", |
| 147 » } | 167 [self description], [[unsatisfiedExpectations |
| 148 » if([exceptions count] > 0) | 168 objectAtIndex:0] description]]; |
| 149 » { | 169 OCMReportFailure(location, description); |
| 150 » » [[exceptions objectAtIndex:0] raise]; | 170 } else if ([unsatisfiedExpectations count] > 0) { |
| 151 » } | 171 NSString* description = [NSString |
| 152 } | 172 stringWithFormat:@"%@: %@ expected methods were not invoked: %@", |
| 153 | 173 [self description], @([unsatisfiedExpectations count]), |
| 154 | 174 [self _stubDescriptions:YES]]; |
| 175 OCMReportFailure(location, description); |
| 176 } |
| 177 |
| 178 if ([exceptions count] > 0) { |
| 179 NSString* description = [NSString |
| 180 stringWithFormat:@"%@: %@ (This is a strict mock failure that was " |
| 181 @"ignored when it actually occured.)", |
| 182 [self description], |
| 183 [[exceptions objectAtIndex:0] description]]; |
| 184 OCMReportFailure(location, description); |
| 185 } |
| 186 |
| 187 return [[[OCMVerifier alloc] initWithMockObject:self] autorelease]; |
| 188 } |
| 189 |
| 190 - (void)verifyWithDelay:(NSTimeInterval)delay { |
| 191 [self verifyWithDelay:delay atLocation:nil]; |
| 192 } |
| 193 |
| 194 - (void)verifyWithDelay:(NSTimeInterval)delay |
| 195 atLocation:(OCMLocation*)location { |
| 196 NSTimeInterval step = 0.01; |
| 197 while (delay > 0) { |
| 198 if ([expectations count] == 0) |
| 199 break; |
| 200 [[NSRunLoop currentRunLoop] |
| 201 runUntilDate:[NSDate dateWithTimeIntervalSinceNow:step]]; |
| 202 delay -= step; |
| 203 step *= 2; |
| 204 } |
| 205 [self verifyAtLocation:location]; |
| 206 } |
| 207 |
| 208 #pragma mark Verify after running |
| 209 |
| 210 - (void)verifyInvocation:(OCMInvocationMatcher*)matcher { |
| 211 [self verifyInvocation:matcher atLocation:nil]; |
| 212 } |
| 213 |
| 214 - (void)verifyInvocation:(OCMInvocationMatcher*)matcher |
| 215 atLocation:(OCMLocation*)location { |
| 216 for (NSInvocation* invocation in invocations) { |
| 217 if ([matcher matchesInvocation:invocation]) |
| 218 return; |
| 219 } |
| 220 NSString* description = |
| 221 [NSString stringWithFormat:@"%@: Method %@ was not invoked.", |
| 222 [self description], [matcher description]]; |
| 223 |
| 224 OCMReportFailure(location, description); |
| 225 } |
| 155 | 226 |
| 156 #pragma mark Handling invocations | 227 #pragma mark Handling invocations |
| 157 | 228 |
| 229 - (id)forwardingTargetForSelector:(SEL)aSelector { |
| 230 if ([OCMMacroState globalState] != nil) { |
| 231 OCMRecorder* recorder = [[OCMMacroState globalState] recorder]; |
| 232 [recorder setMockObject:self]; |
| 233 return recorder; |
| 234 } |
| 235 return nil; |
| 236 } |
| 237 |
| 238 - (BOOL)handleSelector:(SEL)sel { |
| 239 for (OCMInvocationStub* recorder in stubs) |
| 240 if ([recorder matchesSelector:sel]) |
| 241 return YES; |
| 242 |
| 243 return NO; |
| 244 } |
| 245 |
| 158 - (void)forwardInvocation:(NSInvocation *)anInvocation | 246 - (void)forwardInvocation:(NSInvocation *)anInvocation |
| 159 { | 247 { |
| 160 » if([self handleInvocation:anInvocation] == NO) | 248 @try { |
| 161 » » [self handleUnRecordedInvocation:anInvocation]; | 249 if ([self handleInvocation:anInvocation] == NO) |
| 250 [self handleUnRecordedInvocation:anInvocation]; |
| 251 } @catch (NSException* e) { |
| 252 [exceptions addObject:e]; |
| 253 [e raise]; |
| 254 } |
| 162 } | 255 } |
| 163 | 256 |
| 164 - (BOOL)handleInvocation:(NSInvocation *)anInvocation | 257 - (BOOL)handleInvocation:(NSInvocation *)anInvocation |
| 165 { | 258 { |
| 166 » OCMockRecorder *recorder = nil; | 259 [invocations addObject:anInvocation]; |
| 167 » unsigned int» » » i; | 260 |
| 168 » | 261 OCMInvocationStub* stub = nil; |
| 169 » for(i = 0; i < [recorders count]; i++) | 262 for (stub in stubs) { |
| 170 » { | 263 // If the stub forwards its invocation to the real object, then we don't |
| 171 » » recorder = [recorders objectAtIndex:i]; | 264 // want to do handleInvocation: yet, since forwarding the invocation to the |
| 172 » » if([recorder matchesInvocation:anInvocation]) | 265 // real object could call a method that is expected to happen after this |
| 173 » » » break; | 266 // one, which is bad if expectationOrderMatters is YES |
| 174 » } | 267 if ([stub matchesInvocation:anInvocation]) |
| 175 » | 268 break; |
| 176 » if(i == [recorders count]) | 269 } |
| 177 » » return NO; | 270 // Retain the stub in case it ends up being removed from stubs and |
| 178 » | 271 // expectations, since we still have to call handleInvocation on the stub at |
| 179 » if([rejections containsObject:recorder]) | 272 // the end |
| 180 » { | 273 [stub retain]; |
| 181 » » NSException *exception = [NSException exceptionWithName:NSIntern
alInconsistencyException reason: | 274 if (stub == nil) |
| 182 » » » » » » » » [NSString stri
ngWithFormat:@"%@: explicitly disallowed method invoked: %@", [self description]
, | 275 return NO; |
| 183 » » » » » » » » [anInvocation
invocationDescription]] userInfo:nil]; | 276 |
| 184 » » [exceptions addObject:exception]; | 277 if ([expectations containsObject:stub]) { |
| 185 » » [exception raise]; | 278 OCMInvocationExpectation* expectation = [self _nextExptectedInvocation]; |
| 186 » } | 279 if (expectationOrderMatters && (expectation != stub)) { |
| 187 | 280 [NSException raise:NSInternalInconsistencyException |
| 188 » if([expectations containsObject:recorder]) | 281 format:@"%@: unexpected method invoked: %@\n\texpected:\t%@", |
| 189 » { | 282 [self description], [stub description], |
| 190 » » if(expectationOrderMatters && ([expectations objectAtIndex:0] !=
recorder)) | 283 [[expectations objectAtIndex:0] description]]; |
| 191 » » { | 284 } |
| 192 » » » [NSException raise:NSInternalInconsistencyException»
format:@"%@: unexpected method invoked: %@\n\texpected:\t%@", | 285 |
| 193 » » » [self description], [recorder description], [[expectati
ons objectAtIndex:0] description]]; | 286 // We can't check isSatisfied yet, since the stub won't be satisfied until |
| 194 » » » | 287 // we call handleInvocation:, and we don't want to call handleInvocation: |
| 195 » » } | 288 // yes for the reason in the comment above, since we'll still have the |
| 196 » » [[recorder retain] autorelease]; | 289 // current expectation in the expectations array, which will cause an |
| 197 » » [expectations removeObject:recorder]; | 290 // exception if expectationOrderMatters is YES and we're not ready for any |
| 198 » » [recorders removeObjectAtIndex:i]; | 291 // future expected methods to be called yet |
| 199 » } | 292 if (![(OCMInvocationExpectation*)stub isMatchAndReject]) { |
| 200 » [[recorder invocationHandlers] makeObjectsPerformSelector:@selector(hand
leInvocation:) withObject:anInvocation]; | 293 [expectations removeObject:stub]; |
| 201 » | 294 [stubs removeObject:stub]; |
| 202 » return YES; | 295 } |
| 296 } |
| 297 [stub handleInvocation:anInvocation]; |
| 298 [stub release]; |
| 299 |
| 300 return YES; |
| 301 } |
| 302 |
| 303 - (OCMInvocationExpectation*)_nextExptectedInvocation { |
| 304 for (OCMInvocationExpectation* expectation in expectations) |
| 305 if (![expectation isMatchAndReject]) |
| 306 return expectation; |
| 307 return nil; |
| 203 } | 308 } |
| 204 | 309 |
| 205 - (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation | 310 - (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation |
| 206 { | 311 { |
| 207 if(isNice == NO) | 312 if(isNice == NO) |
| 208 { | 313 { |
| 209 » » NSException *exception = [NSException exceptionWithName:NSIntern
alInconsistencyException reason: | 314 [NSException |
| 210 » » » » » » » » [NSString stri
ngWithFormat:@"%@: unexpected method invoked: %@ %@", [self description], | 315 raise:NSInternalInconsistencyException |
| 211 » » » » » » » » [anInvocation
invocationDescription], [self _recorderDescriptions:NO]] userInfo:nil]; | 316 format:@"%@: unexpected method invoked: %@ %@", |
| 212 » » [exceptions addObject:exception]; | 317 [self description], [anInvocation invocationDescription], |
| 213 » » [exception raise]; | 318 [self _stubDescriptions:NO]]; |
| 214 » } | 319 } |
| 215 } | 320 } |
| 216 | 321 |
| 217 | 322 - (void)doesNotRecognizeSelector:(SEL)aSelector __unused { |
| 218 #pragma mark Helper methods | 323 if ([OCMMacroState globalState] != nil) { |
| 219 | 324 // we can't do anything clever with the macro state because we must raise an |
| 220 - (id)getNewRecorder | 325 // exception here |
| 221 { | 326 [NSException raise:NSInvalidArgumentException |
| 222 » return [[[OCMockRecorder alloc] initWithSignatureResolver:self] autorele
ase]; | 327 format:@"%@: Cannot stub/expect/verify method '%@' because no " |
| 223 } | 328 @"such method exists in the mocked class.", |
| 224 | 329 [self description], NSStringFromSelector(aSelector)]; |
| 225 | 330 } else { |
| 226 - (NSString *)_recorderDescriptions:(BOOL)onlyExpectations | 331 [NSException raise:NSInvalidArgumentException |
| 227 { | 332 format:@"-[%@ %@]: unrecognized selector sent to instance %p", |
| 228 » NSMutableString *outputString = [NSMutableString string]; | 333 [self description], NSStringFromSelector(aSelector), |
| 229 » | 334 (void*)self]; |
| 230 » OCMockRecorder *currentObject; | 335 } |
| 231 » NSEnumerator *recorderEnumerator = [recorders objectEnumerator]; | 336 } |
| 232 » while((currentObject = [recorderEnumerator nextObject]) != nil) | 337 |
| 233 » { | 338 #pragma mark Helper methods |
| 234 » » NSString *prefix; | 339 |
| 235 » » | 340 - (NSString*)_stubDescriptions:(BOOL)onlyExpectations { |
| 236 » » if(onlyExpectations) | 341 NSMutableString* outputString = [NSMutableString string]; |
| 237 » » { | 342 for (OCMStubRecorder* stub in stubs) { |
| 238 » » » if(![expectations containsObject:currentObject]) | 343 NSString* prefix = @""; |
| 239 » » » » continue; | 344 |
| 240 » » » prefix = @" "; | 345 if (onlyExpectations) { |
| 241 » » } | 346 if ([expectations containsObject:stub] == NO) |
| 242 » » else | 347 continue; |
| 243 » » { | 348 } else { |
| 244 » » » if ([expectations containsObject:currentObject]) | 349 if ([expectations containsObject:stub]) |
| 245 » » » » prefix = @"expected: "; | 350 prefix = @"expected:\t"; |
| 246 » » » else | 351 else |
| 247 » » » » prefix = @"stubbed: "; | 352 prefix = @"stubbed:\t"; |
| 248 » » } | 353 } |
| 249 » » [outputString appendFormat:@"\n\t%@\t%@", prefix, [currentObject
description]]; | 354 [outputString appendFormat:@"\n\t%@%@", prefix, [stub description]]; |
| 250 » } | 355 } |
| 251 » | 356 return outputString; |
| 252 » return outputString; | 357 } |
| 253 } | 358 |
| 254 | 359 |
| 255 | |
| 256 @end | 360 @end |
| OLD | NEW |