我假设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:忽略错误可能是您不想做的事情...