From b61e42c905fc83a6b1517fa33b8052771908e0f7 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Mon, 28 Apr 2025 18:38:11 +0200 Subject: [PATCH] Allow to mock multiple protocol in a single object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An Objective-C object can naturally implements an arbitrary number of protocol. I ended up in a case where I had to modify my code, because an object was expected to implement two protocols and OCMock does not allow it. I’d love to see this code merged so that it can. I also edited the code for nice mocks in order for the API to be consistent. I don’t actually need it. It should be noted that the description is not changed in the case where there is a single protocol mocked. --- Source/OCMock/OCMockMacros.h | 4 +++ Source/OCMock/OCMockObject.h | 2 ++ Source/OCMock/OCMockObject.m | 10 ++++++ Source/OCMock/OCProtocolMockObject.h | 3 +- Source/OCMock/OCProtocolMockObject.m | 49 ++++++++++++++++++++++++---- 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/Source/OCMock/OCMockMacros.h b/Source/OCMock/OCMockMacros.h index 22535572..c87577d1 100644 --- a/Source/OCMock/OCMockMacros.h +++ b/Source/OCMock/OCMockMacros.h @@ -27,8 +27,12 @@ #define OCMProtocolMock(protocol) [OCMockObject niceMockForProtocol:protocol] +#define OCMProtocolsMock(protocols) [OCMockObject niceMockForProtocols:protocols] + #define OCMStrictProtocolMock(protocol) [OCMockObject mockForProtocol:protocol] +#define OCMStrictProtocolsMock(protocols) [OCMockObject mockForProtocol:protocols] + #define OCMPartialMock(obj) [OCMockObject partialMockForObject:obj] #define OCMObserverMock() [OCMockObject observerMock] diff --git a/Source/OCMock/OCMockObject.h b/Source/OCMock/OCMockObject.h index f5739716..33fff907 100644 --- a/Source/OCMock/OCMockObject.h +++ b/Source/OCMock/OCMockObject.h @@ -36,10 +36,12 @@ + (id)mockForClass:(Class)aClass; + (id)mockForProtocol:(Protocol *)aProtocol; ++ (id)mockForProtocols:(NSArray*)aProtocols; + (id)partialMockForObject:(NSObject *)anObject; + (id)niceMockForClass:(Class)aClass; + (id)niceMockForProtocol:(Protocol *)aProtocol; ++ (id)niceMockForProtocols:(NSArray *)aProtocols; + (id)observerMock __deprecated_msg("Please use XCTNSNotificationExpectation instead."); diff --git a/Source/OCMock/OCMockObject.m b/Source/OCMock/OCMockObject.m index d78cba44..b54d17a7 100644 --- a/Source/OCMock/OCMockObject.m +++ b/Source/OCMock/OCMockObject.m @@ -53,6 +53,11 @@ + (id)mockForProtocol:(Protocol *)aProtocol return [[[OCProtocolMockObject alloc] initWithProtocol:aProtocol] autorelease]; } ++ (id)mockForProtocols:(NSArray *)aProtocol +{ + return [[[OCProtocolMockObject alloc] initWithProtocols:aProtocol] autorelease]; +} + + (id)partialMockForObject:(NSObject *)anObject { return [[[OCPartialMockObject alloc] initWithObject:anObject] autorelease]; @@ -69,6 +74,11 @@ + (id)niceMockForProtocol:(Protocol *)aProtocol return [self _makeNice:[self mockForProtocol:aProtocol]]; } ++ (id)niceMockForProtocols:(NSArray *)aProtocols +{ + return [self _makeNice:[self mockForProtocols:aProtocols]]; +} + + (id)_makeNice:(OCMockObject *)mock { diff --git a/Source/OCMock/OCProtocolMockObject.h b/Source/OCMock/OCProtocolMockObject.h index a6bac51f..4f71d9d7 100644 --- a/Source/OCMock/OCProtocolMockObject.h +++ b/Source/OCMock/OCProtocolMockObject.h @@ -18,9 +18,10 @@ @interface OCProtocolMockObject : OCMockObject { - Protocol *mockedProtocol; + NSArray *mockedProtocols; } - (id)initWithProtocol:(Protocol *)aProtocol; +- (id)initWithProtocols:(NSArray *)aProtocols; @end diff --git a/Source/OCMock/OCProtocolMockObject.m b/Source/OCMock/OCProtocolMockObject.m index 76f44f15..892961c8 100644 --- a/Source/OCMock/OCProtocolMockObject.m +++ b/Source/OCMock/OCProtocolMockObject.m @@ -22,20 +22,44 @@ @implementation OCProtocolMockObject #pragma mark Initialisers, description, accessors, etc. -- (id)initWithProtocol:(Protocol *)aProtocol +- (id)initWithProtocols:(NSArray *)aProtocols { - if(aProtocol == nil) + if(aProtocols == nil) + [NSException raise:NSInvalidArgumentException format:@"Protocols cannot be nil."]; + if([aProtocols count] == 0) + [NSException raise:NSInvalidArgumentException format:@"Protocols cannot be empty."]; + for (Protocol *protocol in aProtocols) { + if(protocol == nil) [NSException raise:NSInvalidArgumentException format:@"Protocol cannot be nil."]; + } [super init]; - mockedProtocol = aProtocol; + mockedProtocols = aProtocols; return self; } +- (id)initWithProtocol:(Protocol *)aProtocol +{ + if(aProtocol == nil) + [NSException raise:NSInvalidArgumentException format:@"Protocol cannot be nil."]; + + return [self initWithProtocols:@[aProtocol]]; +} + - (NSString *)description { - const char *name = protocol_getName(mockedProtocol); + if ([mockedProtocols count] == 1) { + const char *name = protocol_getName(mockedProtocols[0]); return [NSString stringWithFormat:@"OCProtocolMockObject(%s)", name]; + } + + NSMutableString* string = [[NSMutableString alloc] initWithString:@"OCProtoolMockObject(["]; + for (int i = 0; i < [mockedProtocols count]; i++) { + if (i > 0) [string appendString:@", "]; + [string appendFormat:@"%s", protocol_getName(mockedProtocols[i])]; + } + [string appendString:@"])"]; + return string; } #pragma mark Proxy API @@ -45,21 +69,32 @@ - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector struct { BOOL isRequired; BOOL isInstance; } opts[4] = { {YES, YES}, {NO, YES}, {YES, NO}, {NO, NO} }; for(int i = 0; i < 4; i++) { + for (Protocol *mockedProtocol in mockedProtocols) { struct objc_method_description methodDescription = protocol_getMethodDescription(mockedProtocol, aSelector, opts[i].isRequired, opts[i].isInstance); if(methodDescription.name != NULL) - return [NSMethodSignature signatureWithObjCTypes:methodDescription.types]; + return [NSMethodSignature signatureWithObjCTypes:methodDescription.types]; + } } return nil; } - (BOOL)conformsToProtocol:(Protocol *)aProtocol { - return protocol_conformsToProtocol(mockedProtocol, aProtocol); + for (Protocol *mockedProtocol in mockedProtocols) { + + if(protocol_conformsToProtocol(mockedProtocol, aProtocol)) return YES; + } + return NO; } - (BOOL)respondsToSelector:(SEL)selector { - return ([self methodSignatureForSelector:selector] != nil); + for (Protocol *mockedProtocol in mockedProtocols) { + if ([self methodSignatureForSelector:selector] != nil) { + return YES; + } + } + return NO; } @end