OCMock: invokeBlockWithArgs vs checkWithBlock - PullRequest
       95

OCMock: invokeBlockWithArgs vs checkWithBlock

0 голосов
/ 05 августа 2020

Я читаю OCMock ссылку , и меня смущают эти два метода OCMArg invokeBlockWithArgs (раздел 2.6)

Мок-объект вызовет блок, переданный как аргумент заглушенного метода. Если блок принимает аргументы и используется invokeBlock, используются значения по умолчанию для типов аргументов, например, ноль для числового типа. Используя invokeBlockWithArgs: можно указать, с какими аргументами вызывать блок; Необъектные аргументы должны быть заключены в объекты значений, а выражение должно быть заключено в круглые скобки.

и checkWithBlock (раздел 4.3)

Для checkWithSelector: onObject :, когда фиктивный объект получает someMethod :, он вызывает aSelector для anObject. Если метод принимает аргумент, макет передаст аргумент, который был передан someMethod :. Метод должен возвращать логическое значение, указывающее, соответствует ли аргумент ожиданиям или нет. слишком. Итак, когда следует использовать первый или второй метод?

1 Ответ

1 голос
/ 05 августа 2020

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 передавался в качестве определенного аргумента нашему блоку завершения - поэтому в приведенном выше примере наш блок завершения будет вызываться с expectedData как newData и nil (из [NSNull null]) как newError.

...