如何在 Kiwi 中为模拟方法设置参数期望

Posted

技术标签:

【中文标题】如何在 Kiwi 中为模拟方法设置参数期望【英文标题】:How to set expectations on parameters to mocked methods in Kiwi 【发布时间】:2013-03-07 02:59:22 【问题描述】:

使用 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 中有没有更简单的方法来做到这一点?

(我知道我可以在这里使用 hamcrest 匹配器,但目前我正在探索 Kiwi 的能力)。

【问题讨论】:

【参考方案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];

这很好用,而且我发现它比间谍模式更容易实现。但你的问题让我想知道expectations using message patterns。例如:

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

第一个示例将验证 aMockObject 收到带有 isEqual:myArray 参数的 doSomething: 消息。第二个示例将简单地验证 doSomething: 是否已发送,而不需要数组参数。如果我们可以在消息模式中指定某种类型的 Matcher 那就太好了,以表示我们不关心消息中发送的是什么特定的数组实例,只关心它的 count 为 3。

我还没有找到任何能够做到这一点的例子,但看起来有一些可能性。为了验证消息发送期望,Kiwi 使用KWMessagePattern 类,特别是matchesInvocation:argumentFiltersMatchInvocationArguments: 方法。这会检查三种类型的“参数过滤器”:

    文字对象值(如上例中的myArray),与使用isEqual:在消息中发送的实际值进行比较 KWAny 类型的对象(如上例中的 any() 宏),它将匹配任何参数值 满足[KWGenericMatchEvaluator isGenericMatcher:argumentFilter]的对象,基本表示对象响应matches:(id)obj

因此,您应该能够使用在消息模式期望中实现matches: 的对象来执行诸如验证发送到存根方法的数组长度之类的事情,而无需求助于间谍或块。这是一个非常简单的实现:(available as a 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 的内置匹配器类会很好,但我还没有确切地知道如何做到这一点。

【讨论】:

谢谢,这很有趣且内容丰富。 stub:withBlock 似乎没有记录。有点遗憾,与其他存根方法不同,它仅适用于模拟(在我的测试中),但它仍然很有用。还要感谢您非常有用的概述如何为消息发送期望中的参数创建匹配器。 OCMock/OCHamcrest 组合开箱即用,这可能是 Kiwi 进入的有用方向。 谢谢,很好的回答。顺便说一句,在您的 stub:withBlock: 示例中,您应该能够只在块本身内设置对数组的期望,并且根本不需要使用 capturedArray 关于在块内设置期望的好观察。我试过了,效果很好。它也适用于异步测试(示例中的[[aMockObject shouldEventually] receive:] 与块内的普通params[0] should 结合使用)。 @esker“比间谍模式更容易实现”?什么是 stub:withBlock 比 Spy capture 参数更好?

以上是关于如何在 Kiwi 中为模拟方法设置参数期望的主要内容,如果未能解决你的问题,请参考以下文章

如何在 cmock 中为“读取”功能配置期望

如何在 JS(jQuery) 中为翻译添加附加赋值(+=)?

如何使用 Mockery 模拟构造函数

如何在 Spring Boot REST API 中为 db insert 方法编写模拟单元测试?

如何在 Julia 中为可变结构设置默认参数?

如何在 Solidity 中为函数设置默认参数