模拟UITableViewCell并使用isKindOfClass

时间:2013-09-29 13:23:38

标签: objective-c unit-testing ocmock

我正在尝试模拟和测试UITableViewCells以确保我的configureCell:forIndexPath正常工作,除了我无法使用isKindOfClass使其工作但只有conformsToProtocol。这将要求我的所有uitableviewcells都拥有自己的协议,似乎并不需要。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{
  FeedObj *item = [_feedElements objectAtIndex:indexPath.row];
  if( item.obj_type == FeedObjTypeFriendAdd ) {
    MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyTableViewCellIdentifier forIndexPath:indexPath];
    [self configureCell:cell forIndexPath:indexPath]
    return cell;
  } else if( item.obj_type = FeedObjTypeSomeOtherType ) {
    // do another cell
  }
}

- (void)configureCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath 
{
  // only enters conditional in test if I do [cell conformsToProtocol:@protocol(SomeIndividualProtocolForEachTableViewcell)]                  
  if( [cell isKindOfClass:[MyTableViewCell class]] ) {
    // do the configuring
    FeedObj *item = [_streamElements objectAtIndex:indexPath.row];

    NSString *firstName = [item.obj_data objectForKey:@"first_name"];
    NSString *lastName = [item.obj_data objectForKey:@"last_name"];
    NSString *name = [NSString stringWithFormat:@"%@ %@.", firstName, [lastName substringToIndex:1]];
    NSString *text = [NSString stringWithFormat:@"%@ has joined", name];

    [((MyTableViewCell *)cell).messageLabel setText:text];

  } else if( [cell isKindOfClass[SomeOtherTableView class]] ) {
    // do other config
  }
}


    @implementation SampleTests
    - (void)setUp
    {
        _controller = [[MySampleViewController alloc] init];
        _tableViewMock = [OCMockObject niceMockForClass:[UITableView class]];
        [_tableViewMock registerNib:[UINib nibWithNibName:@"MyTableViewCell" bundle:nil] forCellReuseIdentifier:MyTableViewCellIdentifier];
    }

    - (void)testFriendAddCell
    {
        FeedObj *friendAdd = [[FeedObj alloc] init];
        friendAdd.obj_type = FeedObjTypeFriendAdd;
        friendAdd.obj_data = [NSMutableDictionary dictionaryWithDictionary:@{ @"first_name" : @"firstname", @"last_name" : @"lastname" }];
        _mockStreamElements = [NSMutableArray arrayWithObject:friendAdd];
        [_controller setValue:_mockStreamElements forKey:@"_feedElements"];

        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
        [[[_tableViewMock expect] andReturn:[[[NSBundle mainBundle] loadNibNamed:@"MyTableViewCell" owner:self options:nil] lastObject]] dequeueReusableCellWithIdentifier:MyTableViewCellIdentifier forIndexPath:indexPath];

        MyTableViewCell *cell = (MyTableViewCell *)[_controller tableView:_tableViewMock cellForRowAtIndexPath:indexPath];
        STAssertNotNil( cell, @"should not be nil" );
        STAssertTrue( [cell.messageLabel.text isEqualToString:@"firstname l. has joined"], @"should be equal" );
        [_tableViewMock verify];
    }
    @end

我也尝试用[mockCell存根]和返回值:OCMOCK_VALUE((BOOL){YES})] isKindOfClass:[MyTableViewCell类]]]和mockCell期望,它也不起作用。像这样:

id mockCell = [OCMockObject partialMockForObject:[[[NSBundle mainBundle] loadNibNamed:@"MyTableViewCell" owner:self options:nil] lastObject]];
[[[mockCell stub] andReturnValue:OCMOCK_VALUE((BOOL) {YES})] isKindOfClass:[OCMConstraint isKindOfClass:[MyTableViewCell class]]];
[[[_tableViewMock expect] andReturn:mockCell] dequeueReusableCellWithIdentifier:MyTableViewCellIdentifier forIndexPath:indexPath];

我甚至尝试使用http://blog.carbonfive.com/2009/02/17/custom-constraints-for-ocmock/中列出的OCMConstraint。

无论如何都要这样做,还是我必须为每个tableviewcell使用协议?提前致谢

1 个答案:

答案 0 :(得分:1)

我强烈建议您重新考虑如何构建此实现。对于初学者来说,视图控制器非常适合管理视图,但管理模型数据并非如此。它很适合将模型数据传递给它管理的视图,所以考虑到这一点,让我们像这样构建它。

让我们首先介绍一个名为FeedController的新类。该控制器的工作是在VC和支持此屏幕的模型数据之间进行操作。我们假设这个公共接口:

@interface FeedController : NSObject
- (instancetype)initWithFeedArray:(NSArray *)array;
- (NSString *)firstNameAtIndexPath:(NSIndexPath *)path;
- (NSString *)lastNameAtIndexPath:(NSIndexPath *)path;
- (NSString *)fullNameAtIndexPath:(NSIndexPath *)path;
// This should probably have a better name
- (NSString *)textAtIndexPath:(NSIndexPath *)path;
@end

我不会实现这些方法,但它们看起来与您期望的完全一样。初始化程序将复制传入其中的数组,将其存储在ivar中,其他方法将从特定索引处的数组中获取信息并应用您必须的任何自定义转换(例如将名字和姓氏组合在一起)得到全名)。这里的主要目标是传输数据,而不是操纵它。您在视图控制器中尝试和操作此数据的那一刻,就是您将回到原点的那一刻,进行明智的测试。

configureCell:forIndexPath:的对象现在只是从FeedController类传输数据,这是无限简单的测试。无需设置响应链,模拟对象或任何东西。只需提供一些夹具数据即可离开。

您仍在测试构成configureCell:forIndexPath:的部分,但不再直接测试该方法。如果你想确保正确填充视图,那么你应该这样做。但是,您将以不同的方式执行此操作,这不是单元测试的工作。拉出UIAutomation或您最喜欢的UI测试框架,并测试您的UI。使用FeedController上的单元测试来测试数据和转换。

我希望这会有所帮助。