如何模拟符合协议的对象的属性并由OCMock嘲笑?

时间:2013-10-06 14:06:07

标签: ios objective-c unit-testing tdd ocmock

我正在关注Test driven iOS Development中的示例,并且在一个案例中,有一个单元测试可以确保委托获得错误方法的'dumbed down'版本。因此,这里没有涉及太多细节的相关对象:

  • communicator:负责进行网络通话的对象
  • 经理:指示通讯员拨打电话,然后将结果推送给其代表。
  • 委托:符合StackOverflowManagerDelegate协议的经理委托。得到结果并处理它。

所以测试是这样的:

@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");
}

这是StackOverflowManager中searchingForQuestionsFailedWithError的实现,其中管理器只是简化了通信器返回的原始错误,并将愚蠢的版本发送给代理。

- (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];
}

作者建议为此工作..我们实际上必须为管理员委托创建一个模拟对象,如下所示:

@interface MockStackOverflowManagerDelegate : NSObject <StackOverflowManagerDelegate>
@property (strong) NSError *fetchError;

@end

@implementation MockStackOverflowManagerDelegate

@synthesize fetchError;

- (void)fetchingQuestionsOnTopic: (Topic *)topic
                 failedWithError: (NSError *)error {
    self.fetchError = error;
}

@end

这是StackOverflowManagerDelegate

的声明
@protocol StackOverflowManagerDelegate <NSObject>    
- (void)fetchingQuestionsOnTopic: (Topic *)topic
                 failedWithError: (NSError *)error {    
@end

问题我一直在查看本书的所有示例,并试图使用OCMock而不是像作者那样手工制作的那些..(我只是觉得它会是一个更少的时间)。到目前为止一切都工作了..但我被困在这里..如何在委托上伪造一个名为fetchError的属性?这就是我现在所拥有的:

- (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 ");
}

在经理的胆量中,经理在委托上调用fetchingQuestionsOnTopic ..我知道我可以通过using [[[delegate stub] andCall:@selector(differentMethod:) onObject:differentObject] fetchingQuestionsOnTopic:[OCMArg any]]伪造该方法,其中differentMethod会做我想做的任何事情它要做..我只是不知道如何处理differentMethod的结果:我不知道如何将它存储在委托的模拟属性中。


更新:作为以下答案的后续内容。这里是单元测试的实现,可确保代理人仍然可以使用基础错误:

- (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");
}

以下是它的OCMock版本:

- (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];

}

1 个答案:

答案 0 :(得分:1)

试试这个:

- (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];
}

要测试经理报告的错误的域名,代码和userInfo(如果我记得那本书中的另一个测试用例 - 我很久以前就读过它了)你可以这样做:

    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];