Home | History | Annotate | Download | only in OCMock
      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