From c9eb38fa6f1f34a125178d389457a8ecdf9792ad Mon Sep 17 00:00:00 2001 From: Carl Lindberg Date: Tue, 22 Sep 2015 23:20:21 -0400 Subject: [PATCH] Potential solution for mocks where we want to control -respondsToSelector:. Problem noted at http://stackoverflow.com/questions/11923942/ocmock-mocking-protocols-with-excluding-optional-methods . I want to test a class which has a delegate, and the behavior should be tested for situations where the delegate implements an optional method and also when it does not. I could not figure out a way to do this with OCMock other than implementing a slew of individual classes which implement only the desired combination of methods for a particular test. This approach is a subclass of OCProtocolMockObject, which implements -respondsToSelector: based on if there are any stubs, expects, or rejects added to the mock. This way a unit test can control respondsTo status just by manipulating the expects or the stubs. It is not a nice mock, so unexpected calls should still cause exceptions. I'm not sure a "nice" version would make any sense, but it wouldn't to be hard to add if my imagination is just missing something. --- Source/OCMock/OCMockObject.h | 1 + Source/OCMock/OCMockObject.m | 5 +++ Source/OCMock/OCProtocolMockObject.h | 2 ++ Source/OCMock/OCProtocolMockObject.m | 32 +++++++++++++++++-- .../OCMockObjectProtocolMocksTests.m | 22 +++++++++++++ 5 files changed, 60 insertions(+), 2 deletions(-) diff --git a/Source/OCMock/OCMockObject.h b/Source/OCMock/OCMockObject.h index 58df67d7..42ed5754 100644 --- a/Source/OCMock/OCMockObject.h +++ b/Source/OCMock/OCMockObject.h @@ -35,6 +35,7 @@ + (id)mockForClass:(Class)aClass; + (id)mockForProtocol:(Protocol *)aProtocol; ++ (id)respondingMockForProtocol:(Protocol *)aProtocol; + (id)partialMockForObject:(NSObject *)anObject; + (id)niceMockForClass:(Class)aClass; diff --git a/Source/OCMock/OCMockObject.m b/Source/OCMock/OCMockObject.m index cf9d0c5c..1c4acc57 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)respondingMockForProtocol:(Protocol *)aProtocol +{ + return [[[OCRespondingProtocolMockObject alloc] initWithProtocol:aProtocol] autorelease]; +} + + (id)partialMockForObject:(NSObject *)anObject { return [[[OCPartialMockObject alloc] initWithObject:anObject] autorelease]; diff --git a/Source/OCMock/OCProtocolMockObject.h b/Source/OCMock/OCProtocolMockObject.h index 9a102b3f..b3f36965 100644 --- a/Source/OCMock/OCProtocolMockObject.h +++ b/Source/OCMock/OCProtocolMockObject.h @@ -25,3 +25,5 @@ @end +@interface OCRespondingProtocolMockObject : OCProtocolMockObject +@end diff --git a/Source/OCMock/OCProtocolMockObject.m b/Source/OCMock/OCProtocolMockObject.m index 82f61679..1f6b2f88 100644 --- a/Source/OCMock/OCProtocolMockObject.m +++ b/Source/OCMock/OCProtocolMockObject.m @@ -38,18 +38,25 @@ - (NSString *)description #pragma mark Proxy API -- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector isRequired:(BOOL *)required { struct { BOOL isRequired; BOOL isInstance; } opts[4] = { {YES, YES}, {NO, YES}, {YES, NO}, {NO, NO} }; for(int i = 0; i < 4; i++) { struct objc_method_description methodDescription = protocol_getMethodDescription(mockedProtocol, aSelector, opts[i].isRequired, opts[i].isInstance); - if(methodDescription.name != NULL) + if(methodDescription.name != NULL) { + if (required) *required = opts[i].isRequired; return [NSMethodSignature signatureWithObjCTypes:methodDescription.types]; + } } return nil; } +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +{ + return [self methodSignatureForSelector:aSelector isRequired:NULL]; +} + - (BOOL)conformsToProtocol:(Protocol *)aProtocol { return protocol_conformsToProtocol(mockedProtocol, aProtocol); @@ -61,3 +68,24 @@ - (BOOL)respondsToSelector:(SEL)selector } @end + +@implementation OCRespondingProtocolMockObject + +- (BOOL)respondsToSelector:(SEL)aSelector +{ + BOOL required; + NSMethodSignature *sig = [self methodSignatureForSelector:aSelector isRequired:&required]; + return (sig && required) || [self handleSelector:aSelector]; +} + +- (void)stopMocking +{ + expectationOrderMatters = NO; + [stubs release]; stubs = [[NSMutableArray alloc] init]; + [expectations release]; expectations = [[NSMutableArray alloc] init]; + [exceptions release]; exceptions = [[NSMutableArray alloc] init]; + [invocations release]; invocations = [[NSMutableArray alloc] init]; + [super stopMocking]; +} + +@end diff --git a/Source/OCMockTests/OCMockObjectProtocolMocksTests.m b/Source/OCMockTests/OCMockObjectProtocolMocksTests.m index 4c4a6db2..540edb9e 100644 --- a/Source/OCMockTests/OCMockObjectProtocolMocksTests.m +++ b/Source/OCMockTests/OCMockObjectProtocolMocksTests.m @@ -152,4 +152,26 @@ - (void)testRefusesToCreateProtocolMockForNilProtocol XCTAssertThrows(OCMProtocolMock(nil)); } +- (void)testRespondingMockProtocol +{ + id mock = [OCMockObject respondingMockForProtocol:@protocol(TestProtocol)]; + XCTAssertTrue([mock respondsToSelector:@selector(primitiveValue)]); + XCTAssertFalse([mock respondsToSelector:@selector(objectValue)]); + [[mock expect] objectValue]; + XCTAssertTrue([mock respondsToSelector:@selector(objectValue)]); + [mock objectValue]; + XCTAssertFalse([mock respondsToSelector:@selector(objectValue)]); + + [[mock expect] objectValue]; + XCTAssertTrue([mock respondsToSelector:@selector(objectValue)]); + [mock stopMocking]; + XCTAssertFalse([mock respondsToSelector:@selector(objectValue)]); + + [[mock expect] objectValue]; + [[mock reject] objectValue]; + XCTAssertTrue([mock respondsToSelector:@selector(objectValue)]); + [mock objectValue]; + XCTAssertTrue([mock respondsToSelector:@selector(objectValue)]); +} + @end