OCMock:invokeBlockWithArgs 与 checkWithBlock
OCMock: invokeBlockWithArgs vs checkWithBlock
我正在阅读 OCMock reference,我对这两个 OCMArg 方法 invokeBlockWithArgs(第 2.6 节)感到困惑
The mock object will invoke the block passed as an argument to the
stubbed method. If the block takes arguments and invokeBlock is used,
the default values for the argument types are used, e.g. zero for a
numerical type. Using invokeBlockWithArgs: it is possible to specify
which arguments to invoke the block with; non-object arguments must be
wrapped in value objects and the expression must be wrapped in round
brackets.
和 checkWithBlock(第 4.3 节)
For checkWithSelector:onObject:, when the mock object receives
someMethod:, it invokes aSelector on anObject. If the method takes an
argument the mock will pass the argument that was passed to
someMethod:. The method should return a boolean indicating whether the
argument matched the expectation or not.
因此,使用 checkWithBlock 我们可以使用我们提供的任何参数调用传递的块,使用 invokeBlockWithArgs 似乎也可以这样做。那么什么时候使用第一种或者第二种方法呢?
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.whosebug.com"]
completion: ^(NSData *newData, NSError *newError) {
self.lastData = newData;
self.lastError = newError;
}];
}
然后我们可以在我们的测试中像这样测试 getData
方法 class :
@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.whosebug.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.whosebug.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
作为某个参数传递给我们的完成块 - 因此在上面的示例中,我们的完成块将使用 expectedData
作为 newData
,和 nil
(来自 [NSNull null]
)作为 newError
。
我正在阅读 OCMock reference,我对这两个 OCMArg 方法 invokeBlockWithArgs(第 2.6 节)感到困惑
The mock object will invoke the block passed as an argument to the stubbed method. If the block takes arguments and invokeBlock is used, the default values for the argument types are used, e.g. zero for a numerical type. Using invokeBlockWithArgs: it is possible to specify which arguments to invoke the block with; non-object arguments must be wrapped in value objects and the expression must be wrapped in round brackets.
和 checkWithBlock(第 4.3 节)
For checkWithSelector:onObject:, when the mock object receives someMethod:, it invokes aSelector on anObject. If the method takes an argument the mock will pass the argument that was passed to someMethod:. The method should return a boolean indicating whether the argument matched the expectation or not.
因此,使用 checkWithBlock 我们可以使用我们提供的任何参数调用传递的块,使用 invokeBlockWithArgs 似乎也可以这样做。那么什么时候使用第一种或者第二种方法呢?
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.whosebug.com"]
completion: ^(NSData *newData, NSError *newError) {
self.lastData = newData;
self.lastError = newError;
}];
}
然后我们可以在我们的测试中像这样测试 getData
方法 class :
@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.whosebug.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.whosebug.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
作为某个参数传递给我们的完成块 - 因此在上面的示例中,我们的完成块将使用 expectedData
作为 newData
,和 nil
(来自 [NSNull null]
)作为 newError
。