I'm following the examples in Test driven iOS Development and in one case there is a unit test that ensures that a delegate gets a 'dumbed down' version of an error method. So without going into too many details here are the relevant objects:
- communicator: object responsible for making the network calls
- manager: instructs the communicator to make calls and then pushes result to its delegate.
- delegate: manager delegate that conforms to the
StackOverflowManagerDelegate
protocol. gets the results and processes it.
so this is what the test is:
@implementation QuestionCreationTests
{
@private
StackOverflowManager *mgr;
}
- (void)testErrorReturnedToDelegateIsNotErrorNotifiedByCommunicator {
MockStackOverflowManagerDelegate *delegate =
[[MockStackOverflowManagerDelegate alloc] init];
mgr.delegate = delegate;
NSError *underlyingError = [NSError errorWithDomain: @"Test domain"
code: 0 userInfo: nil];
[mgr searchingForQuestionsFailedWithError: underlyingError];
XCTAssertFalse(underlyingError == [delegate fetchError],
@"Error should be at the correct level of abstraction");
}
this is the implementation of searchingForQuestionsFailedWithError
in StackOverflowManager, where the manager simply dumbs down the original error returned by the communicator and sends the dumbed down version to the delegate.
- (void)searchingForQuestionsFailedWithError:(NSError *)error {
NSDictionary *errorInfo = [NSDictionary dictionaryWithObject: error
forKey: NSUnderlyingErrorKey];
NSError *reportableError = [NSError
errorWithDomain: StackOverflowManagerSearchFailedError
code: StackOverflowManagerErrorQuestionSearchCode
userInfo:errorInfo];
[delegate fetchingQuestionsOnTopic: nil
failedWithError: reportableError];
}
the author suggests that for this to work.. we actually have to create a mock object for the manager delegate like so:
@interface MockStackOverflowManagerDelegate : NSObject <StackOverflowManagerDelegate>
@property (strong) NSError *fetchError;
@end
@implementation MockStackOverflowManagerDelegate
@synthesize fetchError;
- (void)fetchingQuestionsOnTopic: (Topic *)topic
failedWithError: (NSError *)error {
self.fetchError = error;
}
@end
this is the declaration of StackOverflowManagerDelegate
:
@protocol StackOverflowManagerDelegate <NSObject>
- (void)fetchingQuestionsOnTopic: (Topic *)topic
failedWithError: (NSError *)error {
@end
Question: I've been going over all the examples of the book and trying to use OCMock instead of the manually made ones like the author is doing.. (i just thought it would be a lot less time consuming). Everything has worked so far.. but i'm stuck here.. how do I fake a property called fetchError
on delegate? This is what I have right now:
- (void)testErrorReturnedToDelegateIsNotErrorNotifiedByCommunicator {
id <StackOverflowManagerDelegate> delegate =
[OCMockObject mockForProtocol:@protocol(StackOverflowManagerDelegate)];
mgr.delegate = delegate;
NSError *underlyingError = [NSError errorWithDomain: @"Test domain"
code: 0 userInfo: nil];
[mgr searchingForQuestionsFailedWithError: underlyingError];
// compiler error here: no known instance method for selector 'fetchError'
XCTAssertFalse(underlyingError == [mgr.delegate fetchError], @"error ");
}
In the guts of manager, manager calls fetchingQuestionsOnTopic
on the delegate.. I know I can fake that method by using [[[delegate stub] andCall:@selector(differentMethod:) onObject:differentObject] fetchingQuestionsOnTopic:[OCMArg any]]
where differentMethod
would do whatever I want it to do.. I just don't know what to do with the result of differentMethod
: I don't know how to store it in a mocked out property of delegate.
update: as a follow up to the answer below.. here is the implementation of unit test that ensures that the underlying error is still made available to the delegate:
- (void)testErrorReturnedToDelegateDocumentsUnderlyingError {
MockStackOverflowManagerDelegate *delegate =
[[MockStackOverflowManagerDelegate alloc] init];
mgr.delegate = delegate;
NSError *underlyingError = [NSError errorWithDomain: @"Test domain"
code: 0 userInfo: nil];
[mgr searchingForQuestionsFailedWithError: underlyingError];
XCTAssertEqual([[[delegate fetchError] userInfo]
objectForKey: NSUnderlyingErrorKey], underlyingError,
@"The underlying error should be available to client code");
}
and here is the OCMock version of it:
- (void)testErrorReturnedToDelegateDocumentsUnderlyingErrorOCMock {
id delegate =
[OCMockObject mockForProtocol:@protocol(StackOverflowManagerDelegate)];
mgr.delegate = delegate;
NSError *underlyingError = [NSError errorWithDomain: @"Test domain"
code: 0 userInfo: nil];
[[delegate expect] fetchingQuestionsFailedWithError:
[OCMArg checkWithBlock:^BOOL(id param) {
return ([[param userInfo] objectForKey:NSUnderlyingErrorKey] == underlyingError);
}]];
[mgr searchingForQuestionsFailedWithError: underlyingError];
[delegate verify];
}