OCMockito验证和NSError **的编译错误

时间:2014-05-22 13:22:36

标签: ios objective-c ocmockito

我无法编译此代码:

[verify(mockedContext) deleteObject:item1];
[verify(mockedContext) deleteObject:item2];
[verify(mockedContext) save:anything()];<--compilation error for conversion id to NSError**

但是,我可以使用given宏在类似情况下通过其他语法传递编译:

    [[given([mockedContext save:nil]) withMatcher:anything()] willReturn:nil];

有什么可以帮我通过验证进行编译吗?

这是编译错误:

Implicit conversion of an Objective-C pointer to 'NSError *__autoreleasing *' is disallowed with ARC

1 个答案:

答案 0 :(得分:2)

我假设'mockedContext'上的save:方法接受指向NSError的指针。

实际上,NSError必须被视为save:方法的额外返回值。这意味着您应该首先设置一个期望。

我找到了一个小例子:

我们从上下文协议开始,采用NSError**的简单方法。

@protocol Context <NSObject>
- (id)doWithError:(NSError *__autoreleasing *)err;
@end

接下来是使用此协议的类,与您的SUT非常相似。我称之为 ContextUsingClass

@interface ContextUsingClass : NSObject

@property (nonatomic, strong) id<Context> context;
@property BOOL recordedError;

- (void)call;

@end

@implementation ContextUsingClass

- (void)call {
    NSError *error;
    [self.context doWithError:&error];
    if (error) {
        self.recordedError = YES;
    }
}

@end

如您所见,当上下文方法doWithError:返回错误时, recordedError 属性设置为YES。在我们的测试中,这是我们可以期望的真实或错误。唯一的问题是,我们如何告诉模拟导致错误(或成功没有错误)?

答案非常直截了当,几乎是你问题的一部分:我们将 OCHamcrest 匹配器传递给given语句,这反过来又会为我们设置错误块。忍受我,我们会到达那里。让我们先写一下拟合匹配器:

typedef void(^ErrorSettingBlock)(NSError **item);

@interface ErrorSettingBlockMatcher : HCBaseMatcher

@property (nonatomic, strong) ErrorSettingBlock errorSettingBlock;

@end


@implementation ErrorSettingBlockMatcher

- (BOOL)matches:(id)item {
    if (self.errorSettingBlock) {
        self.errorSettingBlock((NSError * __autoreleasing *)[item pointerValue]);
    }
    return YES;
}

@end

如果匹配已设置,此匹配器将调用errorSettingBlock,并且在接受所有项目时将始终返回YES。匹配器的唯一目的是设置错误,当测试要求时。从OCMockito issue 22fix开始,我们知道指针到指针都包含在NSValue个对象中,因此我们应该将其解包,然后将其转换为我们众所周知的NSError **

现在终于,这是测试的样子:

@implementation StackOverFlowAnswersTests {
    id<Context> context;
    ContextUsingClass *sut;
    ErrorSettingBlockMatcher *matcher;
}

- (void)setUp {
    [super setUp];
    context = mockProtocol(@protocol(Context));
    sut = [[ContextUsingClass alloc] init];
    sut.context = context;
    matcher = [[ErrorSettingBlockMatcher alloc] init];
}

- (void)testContextResultsInError {
    matcher.errorSettingBlock = ^(NSError **error) {
        *error = [NSError  errorWithDomain:@"dom" code:-100 userInfo:@{}];
    };

    [[given([context doWithError:nil]) withMatcher:matcher] willReturn:nil];
    [sut call];
    assertThatBool(sut.recordedError, is(equalToBool(YES)));
}

- (void)testContextResultsInSuccess {
    [[given([context doWithError:nil]) withMatcher:matcher] willReturn:nil];
    [sut call];
    assertThatBool(sut.recordedError, is(equalToBool(NO)));
}

@end

结论

当您在SUT中调用通过指针指向返回错误的方法时,您应该测试不同的可能结果,而不仅仅是验证方法是否已被调用。

如果你的SUT忽略了错误,那么让你传递给匹配器的块保留一个布尔标志,表明它是这样调用的:

- (void)testNotCaringAboutTheError {
    __block BOOL called = NO;
    matcher.errorSettingBlock = ^(NSError **error) {
        called = YES;
    };

    [[given([context doWithError:nil]) withMatcher:matcher] willReturn:nil];
    [sut call];
    assertThatBool(called, is(equalToBool(YES)));
}

或者通过简单的验证:

- (void)testWithVerifyOnly {
    [sut call];
    [[verify(context) withMatcher:matcher] doWithError:nil];
}

PS:忽略错误可能是你不想做的事情......