1 //--------------------------------------------------------------------------------------- 2 // $Id$ 3 // Copyright (c) 2004-2009 by Mulle Kybernetik. See License file for details. 4 //--------------------------------------------------------------------------------------- 5 6 #import <OCMock/OCMockObject.h> 7 #import "OCClassMockObject.h" 8 #import "OCProtocolMockObject.h" 9 #import "OCPartialMockObject.h" 10 #import "OCObserverMockObject.h" 11 #import <OCMock/OCMockRecorder.h> 12 #import "NSInvocation+OCMAdditions.h" 13 14 @interface OCMockObject(Private) 15 + (id)_makeNice:(OCMockObject *)mock; 16 - (NSString *)_recorderDescriptions:(BOOL)onlyExpectations; 17 @end 18 19 #pragma mark - 20 21 22 @implementation OCMockObject 23 24 #pragma mark Class initialisation 25 26 + (void)initialize 27 { 28 if([[NSInvocation class] instanceMethodSignatureForSelector:@selector(getArgumentAtIndexAsObject:)] == NULL) 29 [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 -force_load linker flag?"]; 30 } 31 32 33 #pragma mark Factory methods 34 35 + (id)mockForClass:(Class)aClass 36 { 37 return [[[OCClassMockObject alloc] initWithClass:aClass] autorelease]; 38 } 39 40 + (id)mockForProtocol:(Protocol *)aProtocol 41 { 42 return [[[OCProtocolMockObject alloc] initWithProtocol:aProtocol] autorelease]; 43 } 44 45 + (id)partialMockForObject:(NSObject *)anObject 46 { 47 return [[[OCPartialMockObject alloc] initWithObject:anObject] autorelease]; 48 } 49 50 51 + (id)niceMockForClass:(Class)aClass 52 { 53 return [self _makeNice:[self mockForClass:aClass]]; 54 } 55 56 + (id)niceMockForProtocol:(Protocol *)aProtocol 57 { 58 return [self _makeNice:[self mockForProtocol:aProtocol]]; 59 } 60 61 62 + (id)_makeNice:(OCMockObject *)mock 63 { 64 mock->isNice = YES; 65 return mock; 66 } 67 68 69 + (id)observerMock 70 { 71 return [[[OCObserverMockObject alloc] init] autorelease]; 72 } 73 74 75 76 #pragma mark Initialisers, description, accessors, etc. 77 78 - (id)init 79 { 80 // no [super init], we're inheriting from NSProxy 81 expectationOrderMatters = NO; 82 recorders = [[NSMutableArray alloc] init]; 83 expectations = [[NSMutableArray alloc] init]; 84 rejections = [[NSMutableArray alloc] init]; 85 exceptions = [[NSMutableArray alloc] init]; 86 return self; 87 } 88 89 - (void)dealloc 90 { 91 [recorders release]; 92 [expectations release]; 93 [rejections release]; 94 [exceptions release]; 95 [super dealloc]; 96 } 97 98 - (NSString *)description 99 { 100 return @"OCMockObject"; 101 } 102 103 104 - (void)setExpectationOrderMatters:(BOOL)flag 105 { 106 expectationOrderMatters = flag; 107 } 108 109 110 #pragma mark Public API 111 112 - (id)stub 113 { 114 OCMockRecorder *recorder = [self getNewRecorder]; 115 [recorders addObject:recorder]; 116 return recorder; 117 } 118 119 120 - (id)expect 121 { 122 OCMockRecorder *recorder = [self stub]; 123 [expectations addObject:recorder]; 124 return recorder; 125 } 126 127 128 - (id)reject 129 { 130 OCMockRecorder *recorder = [self stub]; 131 [rejections addObject:recorder]; 132 return recorder; 133 } 134 135 136 - (void)verify 137 { 138 if([expectations count] == 1) 139 { 140 [NSException raise:NSInternalInconsistencyException format:@"%@: expected method was not invoked: %@", 141 [self description], [[expectations objectAtIndex:0] description]]; 142 } 143 if([expectations count] > 0) 144 { 145 [NSException raise:NSInternalInconsistencyException format:@"%@ : %ld expected methods were not invoked: %@", 146 [self description], (unsigned long)[expectations count], [self _recorderDescriptions:YES]]; 147 } 148 if([exceptions count] > 0) 149 { 150 [[exceptions objectAtIndex:0] raise]; 151 } 152 } 153 154 155 156 #pragma mark Handling invocations 157 158 - (void)forwardInvocation:(NSInvocation *)anInvocation 159 { 160 if([self handleInvocation:anInvocation] == NO) 161 [self handleUnRecordedInvocation:anInvocation]; 162 } 163 164 - (BOOL)handleInvocation:(NSInvocation *)anInvocation 165 { 166 OCMockRecorder *recorder = nil; 167 unsigned int i; 168 169 for(i = 0; i < [recorders count]; i++) 170 { 171 recorder = [recorders objectAtIndex:i]; 172 if([recorder matchesInvocation:anInvocation]) 173 break; 174 } 175 176 if(i == [recorders count]) 177 return NO; 178 179 if([rejections containsObject:recorder]) 180 { 181 NSException *exception = [NSException exceptionWithName:NSInternalInconsistencyException reason: 182 [NSString stringWithFormat:@"%@: explicitly disallowed method invoked: %@", [self description], 183 [anInvocation invocationDescription]] userInfo:nil]; 184 [exceptions addObject:exception]; 185 [exception raise]; 186 } 187 188 if([expectations containsObject:recorder]) 189 { 190 if(expectationOrderMatters && ([expectations objectAtIndex:0] != recorder)) 191 { 192 [NSException raise:NSInternalInconsistencyException format:@"%@: unexpected method invoked: %@\n\texpected:\t%@", 193 [self description], [recorder description], [[expectations objectAtIndex:0] description]]; 194 195 } 196 [[recorder retain] autorelease]; 197 [expectations removeObject:recorder]; 198 [recorders removeObjectAtIndex:i]; 199 } 200 [[recorder invocationHandlers] makeObjectsPerformSelector:@selector(handleInvocation:) withObject:anInvocation]; 201 202 return YES; 203 } 204 205 - (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation 206 { 207 if(isNice == NO) 208 { 209 NSException *exception = [NSException exceptionWithName:NSInternalInconsistencyException reason: 210 [NSString stringWithFormat:@"%@: unexpected method invoked: %@ %@", [self description], 211 [anInvocation invocationDescription], [self _recorderDescriptions:NO]] userInfo:nil]; 212 [exceptions addObject:exception]; 213 [exception raise]; 214 } 215 } 216 217 218 #pragma mark Helper methods 219 220 - (id)getNewRecorder 221 { 222 return [[[OCMockRecorder alloc] initWithSignatureResolver:self] autorelease]; 223 } 224 225 226 - (NSString *)_recorderDescriptions:(BOOL)onlyExpectations 227 { 228 NSMutableString *outputString = [NSMutableString string]; 229 230 OCMockRecorder *currentObject; 231 NSEnumerator *recorderEnumerator = [recorders objectEnumerator]; 232 while((currentObject = [recorderEnumerator nextObject]) != nil) 233 { 234 NSString *prefix; 235 236 if(onlyExpectations) 237 { 238 if(![expectations containsObject:currentObject]) 239 continue; 240 prefix = @" "; 241 } 242 else 243 { 244 if ([expectations containsObject:currentObject]) 245 prefix = @"expected: "; 246 else 247 prefix = @"stubbed: "; 248 } 249 [outputString appendFormat:@"\n\t%@\t%@", prefix, [currentObject description]]; 250 } 251 252 return outputString; 253 } 254 255 256 @end 257