0

我无法编译这段代码:

[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
4

1 回答 1

2

我假设save:“mockedContext”上的方法需要一个指向 NSError 的指针。

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

我做了一个小例子:

我们从Context协议开始,使用一个简单的方法,采用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。这是我们可以期望在我们的测试中是对还是错的事情。唯一的问题是,我们如何告诉 mock 导致错误(或成功而没有错误)?

答案相当直接,几乎是您问题的一部分:我们将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 第 22 期和它的修复中,我们了解到指向指针的指针被包装在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:忽略错误可能是您不想做的事情...

于 2014-05-26T22:29:44.777 回答