我无法编译此代码:
[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
答案 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 22和fix开始,我们知道指针到指针都包含在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:忽略错误可能是你不想做的事情......