(编辑:请参阅 Eugen 的回答和我的评论。他对 OCMockito 的 MKTArgumentCaptor 的使用不仅消除了对FakeNetworkFetcher
.
您的真实代码是异步的,只是因为真实的networkFetcher
. 换个假的。在这种情况下,我会使用手卷假货而不是 OCMockito:
@interface FakeNetworkFetcher : NSObject
@property (nonatomic, strong) NSArray *fakeResult;
@property (nonatomic) BOOL fakeSuccess;
@end
@implementation FakeNetworkFetcher
- (void)fetchInfo:(void (^)(NSArray *result, BOOL success))block {
if (block)
block(self.fakeResult, self.fakeSuccess);
}
@end
有了这个,你可以为你的测试创建辅助函数。我假设您的被测系统位于测试夹具中,名为 ivar sut
:
- (void)setUpFakeNetworkFetcherToSucceedWithResult:(NSArray *)fakeResult {
sut.networkFetcher = [[FakeNetworkFetcher alloc] init];
sut.networkFetcher.fakeSuccess = YES;
sut.networkFetcher.fakeResult = fakeResult;
}
- (void)setUpFakeNetworkFetcherToFail
sut.networkFetcher = [[FakeNetworkFetcher alloc] init];
sut.networkFetcher.fakeSuccess = NO;
}
现在您的成功路径测试需要确保您的表格视图重新加载了更新的模型。这是第一次天真的尝试:
- (void)testReloadTableViewContents_withSuccess_ShouldReloadTableWithResult {
// given
[self setUpFakeNetworkFetcherToSucceedWithResult:@[@"RESULT"]];
sut.tableView = mock([UITablewView class]);
// when
[sut reloadTableViewContents];
// then
assertThat(sut.model, is(@[@"RESULT"]));
[verify(sut.tableView) reloadData];
}
不幸的是,这并不能保证模型在reloadData
消息之前更新。但是无论如何,您都需要进行不同的测试,以确保获取的结果在表格单元格中表示。这可以通过保留真正的 UITableView 并允许运行循环使用这个帮助方法来完成:
- (void)runForShortTime {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
}
最后,这是一个对我来说开始看起来不错的测试:
- (void)testReloadTableViewContents_withSuccess_ShouldShowResultInCell {
// given
[self setUpFakeNetworkFetcherToSucceedWithResult:@[@"RESULT"]];
// when
[sut reloadTableViewContents];
// then
[self runForShortTime];
NSIndexPath *firstRow = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *firstCell = [sut.tableView cellForRowAtIndexPath:firstRow];
assertThat(firstCell.textLabel.text, is(@"RESULT"));
}
但是您的真正测试将取决于您的单元格如何实际代表获取的结果。这表明这个测试是脆弱的:如果你决定改变表示,那么你必须去修复一堆测试。所以让我们提取一个辅助断言方法:
- (void)assertThatCellForRow:(NSInteger)row showsText:(NSString *)text {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
UITableViewCell *cell = [sut.tableView cellForRowAtIndexPath:indexPath];
assertThat(cell.textLabel.text, is(equalTo(text)));
}
有了这个,这里有一个测试,它使用我们的各种帮助方法来表达并且非常健壮:
- (void)testReloadTableViewContents_withSuccess_ShouldShowResultsInCells {
[self setUpFakeNetworkFetcherToSucceedWithResult:@[@"FOO", @"BAR"]];
[sut reloadTableViewContents];
[self runForShortTime];
[self assertThatCellForRow:0 showsText:@"FOO"];
[self assertThatCellForRow:1 showsText:@"BAR"];
}
请注意,当我开始时,我的脑海中并没有这个结局。我什至在我没有展示的过程中做了一些错误的步骤。但这显示了我如何尝试迭代测试设计的方式。
编辑:我现在看到,使用我的 FakeNetworkFetcher,块在中间执行reloadTableViewContents
——这并不能反映异步时真正发生的情况。通过转移到捕获块然后根据 Eugen 的回答调用它,该块将在reloadTableViewContents
完成后执行。这要好得多。
- (void)testReloadTableViewContents_withSuccess_ShouldShowResultsInCells {
[sut reloadTableViewContents];
[self simulateNetworkFetcherSucceedingWithResult:@[@"FOO", @"BAR"]];
[self runForShortTime];
[self assertThatCellForRow:0 showsText:@"FOO"];
[self assertThatCellForRow:1 showsText:@"BAR"];
}