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