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:
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];
}
Try this:
- (void)testErrorReturnedToDelegateIsNotErrorNotifiedByCommunicator {
id delegate =
[OCMockObject mockForProtocol:@protocol(StackOverflowManagerDelegate)];
mgr.delegate = delegate;
NSError *underlyingError = [NSError errorWithDomain: @"Test domain"
code: 0 userInfo: nil];
[[delegate expect] fetchingQuestionsOnTopic:OCMOCK_ANY
failedWithError:[OCMArg isNotEqual:underlyingError]];
[mgr searchingForQuestionsFailedWithError:underlyingError];
[delegate verify];
}
To test the domain, code and userInfo of the error reported by the manager (if I remember well it's another test case in that book - I read it long time ago) you could do something like this:
id <StackOverflowManagerDelegate> delegate =
[OCMockObject mockForProtocol:@protocol(StackOverflowManagerDelegate)];
mgr.delegate = delegate;
NSError *underlyingError = [NSError errorWithDomain: @"Test domain"
code: 0 userInfo: nil];
NSError *expectedError = [NSError errorWithDomain:@"expected domain"
code:0/* expected code here*/
userInfo:@{NSUnderlyingErrorKey: underlyingError}];
[[delegate expect] fetchingQuestionsOnTopic:OCMOCK_ANY
failedWithError:expectedError];
[mgr searchingForQuestionsFailedWithError: underlyingError];
[delegate verify];