【问题标题】:How to stub method block in Kiwi?如何在 Kiwi 中存根方法块?
【发布时间】:2013-10-24 07:14:50
【问题描述】:

我想使用 Kiwi 存根一个将块作为参数的方法。以下是完整的代码解释:

我有一个名为TestedClass 的类,它有一个方法testedMethod,它依赖于通过AFNetworking 调用服务器的类NetworkClass,并通过块返回其响应。翻译成代码:

@interface TestedClass : NSObject
    -(void)testMethod;
@end

-(void)testMethod
{
    NetworkClass *networkClass = [[NetworkClass alloc] init];

    [networkClass networkMethod:^(id result)
    {
        // code that I want to test according to the block given which I want to stub
        ...
    }];
}



typedef void (^NetworkClassCallback)(id result);

 @interface NetworkClass : NSObject
 -(void)networkMethod:(NetworkClassCallback)handler;
 @end

-(void) networkMethod:(NetworkClassCallback)handler
{
    NSDictionary *params = @{@"param":@", @"value"};
    NSString *requestURL = [NSString stringWithFormat:@"www.someserver.com"];
    AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURLURLWithString:requestURL]];
    NSURLRequest *request = [httpClient requestWithMethod:@"GET" path:requestURL parameters:params];
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];

    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
         handler(responseObject);
    }

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    handler(nil);
}];

[operation start];
}

如何使用 Kiwi 将 networkMethod 与块一起存根以便对 testMethod 进行单元测试?

更新:找到如何在 Kiwi 中执行此操作,请参阅下面的答案。

【问题讨论】:

    标签: objective-c unit-testing mocking tdd kiwi


    【解决方案1】:

    这是您在 Kiwi 中执行此操作的方法:

    首先,你必须依赖注入NetworkClassTestedClass(如果不清楚,请添加评论,我会解释;为简单起见,这可以作为一个属性来完成。这样你就可以操作在NetworkClass 的模拟对象上)

    然后你的规范,为网络类创建模拟并创建你想要单元测试的类:

    SPEC_BEGIN(TestSpec)
    
    describe(@"describe goes here", ^{
        it(@"should test block", ^{
            NetworkClass *mockNetworkClass = [NetworkClass mock];
            KWCaptureSpy *spy = [mockNetworkClass captureArgument:@selector(networkMethod:) atIndex:0];
            TestedClass testClass = [TestedClass alloc] init];
            testClass.networkClass = mockNetworkClass;
            [testClass testMethod];
    
            NetworkClassCallback blockToRun = spy.argument;
            blockToRun(nil);
    
            // add expectations here
    
        });
    });
    
    SPEC_END
    

    解释这里发生了什么:

    您正在创建TestedClass 并调用testMethod。然而,在此之前,我们正在创建一个名为Spy 的东西——它的工作是在调​​用networkMethod: 时捕获第一个参数中的块。现在,是时候实际执行块本身了。

    这里很容易混淆,所以我要强调一点:调用的顺序很重要;你首先声明 spy,然后调用被测试的方法,然后你才真正调用并执行块!

    这将使您能够检查您想要什么,因为您是执行该块的人。

    希望它对其他人有所帮助,因为我花了很长时间才理解这个流程。

    【讨论】:

    • 如果您不想暴露TestedClass NetworkClass 实例怎么办? (它是readonly 还是私有财产)?这可能吗?
    • 是否需要依赖注入才能使这种规范工作?该模式听起来很有趣,但现在不想花时间。此外,对 *spy 的分配是否应该调用 [mockNetworkClass captureArgument: ... (而不是 networkClass)或者我错过了什么?
    • @Byofuel 是的,您必须提供模拟对象 - 这是通过赋值完成的:testClass.networkClass = mockNetworkClass,感谢您的错字;)
    • @AdamWaite 抱歉回复晚了!在您的规范文件中,您可以添加一个testedClass 类别并仅在其中公开该属性 - 这样您就可以提供所需的封装,同时能够注入该属性。
    • @Byofuel FTI,我刚刚尝试在没有模拟的情况下在真实对象上调用captureArgument,它确实执行了该块,但是,我不会这样做,因为真正的实现可能也会调用块,这可能会产生副作用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-30
    • 1970-01-01
    • 1970-01-01
    • 2013-08-22
    • 2019-04-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多