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