Objective c OcMock-存根/预期/停止模拟

Objective c OcMock-存根/预期/停止模拟,objective-c,ocmock,Objective C,Ocmock,在我的夹具设置中,我有以下内容 -(void)setUp{ _vc = [OCMockObject partialMockForObject:[[InboxViewController alloc] init]]; //stub out the view stuff [[_vc stub] removeTask:OCMOCK_ANY]; [[_vc stub] insertTask:OCMOCK_ANY]; } 夹具中有15个测试,但是,我需要实际测试这两个方法

在我的夹具设置中,我有以下内容

-(void)setUp{
    _vc = [OCMockObject partialMockForObject:[[InboxViewController alloc] init]];
    //stub out the view stuff
    [[_vc stub] removeTask:OCMOCK_ANY];
    [[_vc stub] insertTask:OCMOCK_ANY];
}
夹具中有15个测试,但是,我需要实际测试这两个方法是否被调用,所以我编写了两个测试

-(void)someTest{
    [[_vc expect] removeTask:OCMOCK_ANY];
    [_vc removeAllTasksFromList:taskList notInList:newTaskList];
    [_vc verify];
}
但这项测试失败了

我也试过了

-(void)someTest{
    [[_vc stopMocking];
    [[_vc expect] removeTask:OCMOCK_ANY];
    [[_vc stub] removeTask:OCMOCK_ANY];
    [_vc removeAllTasksFromList:taskList notInList:newTaskList];
    [_vc verify];
} 
但测试仍然失败。是我遗漏了什么,还是OCMock就是这样工作的

我唯一能让它工作的方法就是这样

-(void)someTest{
    //re create and init the mock object 
    _vc = [OCMockObject partialMockForObject:[[InboxViewController alloc] init]]; 
    [[_vc expect] removeTask:OCMOCK_ANY]; 
    [[_vc stub] removeTask:OCMOCK_ANY]; 
    [_vc removeAllTasksFromList:taskList notInList:newTaskList]; 
    [_vc verify]; 
}

似乎您需要创建一个新的mock来期望一个您已经存根的方法。 我建议您重新考虑在所有测试用例中使用部分模拟,如果您愿意,请提取以下内容:

[[_vc stub] removeTask:OCMOCK_ANY];
[[_vc stub] insertTask:OCMOCK_ANY];
并从您真正需要的测试中调用该方法,将其从设置方法中删除


小提示:),您应该在
设置
实现开始时调用
[super setUp]

存根调用和expect调用的顺序对于部分模拟非常重要。在预期之前对方法进行存根将意味着发送到存根方法的任何消息都不会满足预期

另一方面,stopMocking仅仅意味着部分模拟将停止与真实对象关联。mock仍然保留着它的记录器(存根和期望值),并照常工作。您可以通过向真实对象发送
removeAllTasksFromList:notInList
来验证这一点,在这种情况下,真实对象没有分配给任何变量。在本例中,您将看到消息确实到达了对象的实现。不过,mock仍然会使verify调用失败。一般来说,应该对真实对象调用方法。部分模拟仍将拦截消息

正如在另一个答案中提到的,最好的解决方法是在存根方法之前创建一个新的部分mock和调用expect。您甚至可以实现一个助手:

- (void) setUpPartialMockWithExpect:(BOOL)needExpect {
    _vc = [OCMockObject partialMockForObject:[[InboxViewController alloc] init]];

    if( needExpect )
        [[_vc expect] removeTask:OCMOCK_ANY];

    //stub out the view stuff
    [[_vc stub] removeTask:OCMOCK_ANY];
    [[_vc stub] insertTask:OCMOCK_ANY];
}

在你的每个
-(void)测试中…

需要注意以下几点:

  • 您应该保留对正在模拟的实际对象的引用。在模拟引用上调用模拟设置/验证,以及在实际对象上测试的方法
  • 您不应该存根并期望相同的方法调用。存根将匹配,期望值不会通过
  • 我假设您正在插入
    设置
    ,因为您有一些方法可能在测试中调用,也可能不调用。如果是这样,您可以这样构造测试:

    static InboxViewController *_vc;
    static id mockInbox;
    
    -(void)setUp{
        _vc = [[InboxViewController alloc] init];
        mockInbox = [OCMockObject partialMockForObject:_vc];
        //stub out the view stuff
        [[mockInbox stub] removeTask:OCMOCK_ANY];
        [[mockInbox stub] insertTask:OCMOCK_ANY];
    }
    
    -(void)someTest{
        [[mockInbox expect] somethingIExpectForThisTest:OCMOCK_ANY];
        [_vc removeAllTasksFromList:taskList notInList:newTaskList];
        [mockInbox verify];
    }
    
    -(void)someOtherTest{
        [[mockInbox expect] someOtherThingIExpectForThisTest:OCMOCK_ANY];
        [_vc doSomethingElse];
        [mockInbox verify];
    }
    

    也许文档应该更清晰。StopMock对部分模拟的作用是将真实对象(在您的例子中是InboxViewController)恢复到其原始状态。调用stopMocking不会重置mock对象,这意味着它不会清除存根和期望值。您始终可以调用stopMocking,然后为同一真实对象创建新的mock

    正如在另一个答案中指出的那样,通常最好避免使用stub和expect相同的方法,但是如果必须这样做,请确保在使用stub之前设置expect;否则,存根将处理调用,expect将永远看不到它们


    我知道传统上很多人建议使用设置方法来设置测试主题。多年来,我个人的经验是,这通常是不值得的。在每个测试中保存几行可能看起来很吸引人,但最终它确实会在各个测试之间创建耦合,使套件更加脆弱。

    您可以执行以下操作来删除OCMockObject中的存根,这将使您能够在
    -(void)设置中保留
    存根
    ,但是仍然允许您在以后的测试中添加
    expect

    将以下类别添加到测试中以返回OCMockObject ivar

    @interface OCMockObject (Custom)
    - (void)removeStubWithName:(NSString*)stubName;
    @end
    
    @implementation OCMockObject (Custom)
    - (void)removeStubWithName:(NSString*)stubName {
        NSMutableArray* recordersToRemove = [NSMutableArray array];
        for (id rec in recorders) {
            NSRange range = [[rec description] rangeOfString:stubName];
            if (NSNotFound == range.location) continue;
            [recordersToRemove addObject:rec];
        }
        [recorders removeObjectsInArray:recordersToRemove];
    }
    @end
    
    用法示例: 在添加expect之前,请确保删除方法的存根

    -(void)someTest{
        [_vc removeStubWithName:@"removeTask"];
        [[_vc expect] removeTask:OCMOCK_ANY];
        [_vc removeAllTasksFromList:taskList notInList:newTaskList];
        [_vc verify];
    }
    
    这将允许您的测试按预期运行


    这是一个有用的黑客,至少在OCMock开发人员允许此功能之前是如此。

    这只是一个代码示例。我实际上是在呼叫超级设置。看来在怀孕前打短发是行不通的。但我不明白的是,为什么不停止模仿,然后期待stub工作得足够有趣,这就引发了ocmock的另一个问题。你不能在期待之前就放弃。它没有通过测试。实际上,我已经向ocmock开发者询问过这一点,但还没有收到回应。答案中的提示有更多的细节,但基本上,如果您首先创建存根,它将处理所有调用,期望永远不会看到它,然后模拟将在verify中抱怨。通常情况下,您要么希望使用一个方法,要么希望使用存根,但不能同时使用这两个方法。我想OCMock中仍然缺少的是一个“至少一次”修饰符,因此期望可以多次调用的方法变得更容易。关于提取测试主题如何创建耦合的观点很好!这是正确的答案和一个可爱的黑客。谢谢