Как установить ожидания параметров для имитируемых методов в Kiwi

Используя OCMockito и OCHamcrest, я могу настроить ожидания аргументов для имитируемых методов, таким образом:

[verify(aMockObject) doSomething:allOf(is(instanceOf([NSArray class])), hasCountOf(3U), nil)];

Кажется, не существует такого же простого способа сделать это с помощью Kiwi. Можно перехватить аргументы с помощью шпиона, например:

KWCaptureSpy *spy = [aMockObject captureArgument:@selector(doSomething:) atIndex:0];
NSArray *capturedArray = spy.argument;

А затем проверить ожидания по захваченному объекту:

[[capturedArray should] haveCountOf:3U];

Есть ли менее неуклюжий способ сделать это в Киви?

(Я знаю, что, вероятно, мог бы использовать здесь сопоставители подколенного сухожилия, но на данный момент я изучаю, на что способен Kiwi).


person Cris    schedule 07.03.2013    source источник


Ответы (1)


Один из вариантов, который я использовал, это stub:withBlock:

NSArray* capturedArray; // declare this as __block if needed
[aMockObject stub:@selector(doSomething:)
        withBlock:^id(NSArray *params) {
            capturedArray = params[0];
            // this is necessary even if the doSomething method returns void
            return nil;
        }];
// exercise your object under test, then:
[[capturedArray should] haveCountOf:3U];

Это прекрасно работает, и мне кажется, что его проще реализовать, чем шпионский шаблон. Но ваш вопрос заставил меня задуматься об ожиданиях при использовании шаблонов сообщений. Например:

[[[aMockObject should] receive] doSomething:myArray];
[[[aMockObject should] receive] doSomething:any()];

В первом примере проверяется, что aMockObject получил сообщение doSomething: с аргументом, который isEqual:myArray. Второй пример просто проверит, что doSomething: было отправлено, не ожидая аргумента массива. Было бы здорово, если бы мы могли указать какой-либо тип Matcher в шаблоне сообщения, чтобы показать, что нам все равно, какой конкретный экземпляр массива отправляется в сообщении, только то, что он имеет count из 3.

Я не нашел примеров того, как это сделать, но похоже, что есть некоторые возможности. Чтобы проверить ожидание отправки сообщения, Kiwi использует класс KWMessagePattern, а именно методы matchesInvocation: и argumentFiltersMatchInvocationArguments:. Это проверяет три типа «фильтров аргументов»:

  1. Буквенные значения объекта (например, myArray в приведенном выше примере), которые сравниваются с фактическим значением, отправленным в сообщении с использованием isEqual:.
  2. Объект типа KWAny (например, макрос any() в приведенном выше примере), который будет соответствовать любому значению аргумента.
  3. Объекты, которые удовлетворяют [KWGenericMatchEvaluator isGenericMatcher:argumentFilter], что в основном означает, что объект отвечает на matches:(id)obj

Таким образом, вы должны иметь возможность использовать объекты, которые реализуют matches: в ожиданиях шаблона сообщения, чтобы делать такие вещи, как проверка длины массивов, отправляемых в заглушки, не прибегая к шпионам или блокам. Вот очень простая реализация: (доступно как Gist)

// A reusable class that satisfies isGenericMatcher:
@interface SOHaveCountOfGenericMatcher : NSObject
- (id)initWithCount:(NSUInteger)count;
- (BOOL)matches:(id)item; // this is what KWMessagePattern looks for
@property (readonly, nonatomic) NSUInteger count;
@end

@implementation SOHaveCountOfGenericMatcher
- (id)initWithCount:(NSUInteger)count
{
    if (self = [super init]) {
        _count = count;
    }
    return self;
}
- (BOOL)matches:(id)item
{
    if (![item respondsToSelector:@selector(count)])
        return NO;
    return [item count] == self.count;
}
@end

// Your spec:
it(@"should receive an array with count 3", ^{
    NSArray* testArray = @[@"a", @"b", @"c"];
    id argWithCount3 = [[SOHaveCountOfGenericMatcher alloc] initWithCount:3];
    id aMockObject = [SomeObj nullMock];
    [[[aMockObject should] receive] doSomething:argWithCount3];
    [aMockObject doSomething:testArray];
});

Было бы неплохо иметь возможность повторно использовать здесь встроенные классы сопоставления Kiwi, но я еще не выяснил, как именно это сделать.

person Mike Mertsock    schedule 28.03.2013
comment
Спасибо, интересно и познавательно. stub:withBlock кажется недокументированным. Немного жаль, что в отличие от других методов-заглушек, он работает (в моем тестировании) только на макетах, но, тем не менее, он полезен. Спасибо также за ваш очень полезный обзор того, как создавать сопоставители для аргументов в ожиданиях отправки сообщений. Комбинация OCMock / OCHamcrest делает это из коробки, что может быть полезным для Kiwi. - person Cris; 29.03.2013
comment
Спасибо, отличный ответ. Кстати, в вашем примере stub:withBlock: вы должны просто установить ожидание массива внутри самого блока и вообще не использовать capturedArray. - person Adam Sharp; 19.04.2013
comment
Хорошее замечание о настройке ожидания внутри блока. Я попробовал это, и это работает хорошо. Он также работает с асинхронным тестированием ([[aMockObject shouldEventually] receive:] в примере в сочетании с простым params[0] should внутри блока). - person Mike Mertsock; 19.04.2013
comment
@esker легче реализовать, чем шпионский шаблон? Чем stub:withBlock лучше аргумента захвата шпиона? - person onmyway133; 12.06.2014