首页 > 解决方案 > OCMock:invokeBlockWithArgs 与 checkWithBlock

问题描述

我正在阅读 OCMock参考资料,我对这两个 OCMArg 方法 invokeBlockWithArgs 感到困惑(第 2.6 节)

模拟对象将调用作为参数传递给存根方法的块。如果块接受参数并使用invokeBlock,则使用参数类型的默认值,例如数字类型为零。使用 invokeBlockWithArgs:可以指定调用块的参数;非对象参数必须包裹在值对象中,表达式必须包裹在圆括号中。

和 checkWithBlock(第 4.3 节)

对于 checkWithSelector:onObject:,当 mock 对象接收到 someMethod: 时,它会在 anObject 上调用 aSelector。如果该方法接受一个参数,则模拟将传递传递给 someMethod: 的参数。该方法应返回一个布尔值,指示参数是否符合预期。

因此,使用 checkWithBlock 我们可以使用我们提供的任何参数调用传递的块,并且使用 invokeBlockWithArgs 似乎也可以这样做。那么什么时候应该使用第一种或第二种方法呢?

标签: iosobjective-cocmock

解决方案


checkWithBlock- 您提供一个块,该块将被调用以断言传递给您与之交换的存根方法的值[OCMArg checkWithBlock:]符合您的期望。

invokeBlockWithArgs- 这可以在存根块参数时使用,以使用示例参数调用它们。如果要检查块的行为,则需要这样做。


假设我们有一个Networking只有一个方法的简单客户端:

- (void)call:(NSURL *)url completion: (^void(NSData *, NSError *));

我们还有一些ModelClass将我们的实例Networking作为依赖项init,看起来像这样:

@property (nonatomic, nullable, strong) NSData *lastData;
@property (nonatomic, nullable, strong) NSError *lastError;

@property (nonatomic, strong) Networking *networking;

- (instancetype)initWith:(Networking *)networking { /* */ }

- (void)getData {
    [self.networking call:[NSURL URLWithString:@"www.stackoverflow.com"]
               completion: ^(NSData *newData, NSError *newError) {
                             self.lastData = newData;
                             self.lastError = newError;
               }];
}

然后我们可以getData在我们的测试类中测试这样的方法:

@property (nonatomic, strong) Networking *networkingMock;
@property (nonatomic, strong) ModelClass *model;

- (void)setUp {
    [super setUp];

    self.networkingMock = OCMClassMock([Networking class]);
    self.model = [[ModelClass alloc] initWith:self.networkingMock];
}

// Assert proper argument was passed by explicitly providing
// expected value in `OCMStub`/`OCMExpect` call
// OCMock will check that they are equal for us
- (void)test_getData_passesCorrectURL {

    // Arrange
    OCMExpect([self.networkingMock call:[NSURL URLWithString:@"www.stackoverflow.com"] 
                             completion:OCMOCK_ANY]);

    // Act
    [self.model getData];

    // Assert
    OCMVerifyAll(self.networkingMock);
}

// Assert proper argument is passed in a custom assertion block
// OCMock will call this block, passing the value so that we can inspect it
// We cannot use `invokeBlockWithArgs` to check the `url` parameter
// because its not a block.
- (void)test_getData_passesCorrectURL_withCheckWithBlock {
    // Arrange
    OCMExpect([self.networkingMock call:[OCMArg checkWithBlock:^BOOL(id value) {
                             // This is the custom assertion block, we can inspect the `value` here
                             // We need to return `YES`/`NO` depending if it matches our expectetations

                             if (![value isKindOfClass:[NSURL class]]) { return NO };
                             NSURL *valueAsURL = (NSURL *)value;
                             return [valueAsURL isEqualToURL:[NSURL URLWithString:@"www.stackoverflow.com"]];
                             }] 
                             completion:OCMOCK_ANY]);

    // Act
    [self.model getData];

    // Assert
    OCMVerifyAll(self.networkingMock);
}

// We want to assert the behavior of the completion block passed to the `Networking`
// in the `getData` method. So we need a way to invoke this block somehow - 
// in previous two tests it was never called, because `OCMock` replaces the 
// implementations of methods in stubbed classes.
- (void)test_getData_shouldSetLastData_onCompletion {

    // Arrange
    NSData *expectedData = [NSData data];
    OCMExpect([self.networkingMock call:OCMOCK_ANY
                             completion:[OCMArg invokeBlockWithArgs:expectedData, [NSNull null], nil]]);

    // Act
    [self.model getData];

    // Assert
    XCTAssertEqualObjects(self.model.lastData, expectedData);

}

在最后一个例子中,如果你使用checkWithBlock了而不是invokeBlockWithArgs,传入的完成块ModelClass将不会被调用。相反,将调用自定义断言块(正如我们在第二个测试中所见),并将指向完成块的指针作为值传递。

您当然可以将此指针转换为块类型,并使用一些参数自己调用块 - 但由于invokeBlockWithArgs.

注意: invokeBlockWithArgs采用var_args需要终止的参数列表nil来指示结束。我们需要使用[NSNull null]来指示我们希望nil作为某个参数传递给我们的完成块 - 所以在上面的示例中,我们的完成块将被调用expectedDataasnewDatanil(from [NSNull null]) as newError


推荐阅读