2014-02-14 129 views
4

我正在尝试为使用Parse后端框架的iOS应用程序编写单元测试,并且在尝试编写成功的单元测试后,大量实验似乎失败了。我在测试异步代码(Testing asynchronous call in unit test in iOS)和测试网络调用时发现了几篇文章,但我还没有找到使用异步回调测试对Parse后端调用的方法。单元测试解析框架iOS

举个例子,任何人都可以建议我怎么会测试下面的代码行:

[PFUser saveUser:userModelMock withBlock:^(BOOL success, NSError *error) { 

}]; 

我使用OCMock和XCTest框架。

任何帮助将不胜感激。

*编辑* 这是我到目前为止,但似乎未能

- (void)testSaveUser { 
    id userModelMock = [OCMockObject niceMockForClass:[UserModel class]]; 
    id userControllerMock = [OCMockObject niceMockForClass:[self.userController class]]; 

    [[userModelMock expect] saveInBackgroundWithBlock:[OCMArg any]]; 
    [[userControllerMock stub] saveUser:userModelMock withBlock:[OCMArg any]]; 

    [userModelMock verify]; 
} 
+1

它是什么,你想测试? PFUser希望有它自己的单元测试,所以你的测试应该依靠它作为广告运行,你可以简单地嘲笑调用saveUser:withBlock :.如果你想测试你的异步回调是否按预期工作,这个答案可能会有所帮助:http://stackoverflow.com/a/20694495/449161 –

+0

同意,我会想象解析有内部测试 - 但我是PFUser的子类,并已创建一个处理所有数据交互的控制器。在控制器中,我有一个调用saveUser的方法,所以在测试中我需要验证saveUser:当我调用包装它的方法时调用它。 –

+0

如果您正在测试控制器是否从另一种方法调用此方法,则只需使用mock和'expect“作为PFUser对象并从测试中调用控制器方法。如果该调用是异步的,请按照您链接的帖子中的模型进行操作。或者我错过了什么? –

回答

4

如果你不介意利用第三方库,您可以用下面的办法:

#import <XCTest/XCTest.h> 
#import <RXPromise/RXPromise.h> 
... 

@interface PFUserTests : XCTestCase 
@end 

@implementation PFUserTests 

// helper: 
- (RXPromise*) saveUser:(User*)user { 
    RXPromise* promise = [[RXPromise alloc] init]; 
    [PFUser saveUser:user withBlock:^(Bool success, NSError*error){ 
     if (success) { 
      [promise fulfillWithValue:@"OK"]; 
     } 
     else { 
      [promise rejectWithReason:error]; 
     } 
    }]; 
    return promise; 
} 


// tests: 

-(void) testSaveUser 
{ 
    User* user = ... ; 
    RXPromise* promise = [self saveUser:user]; 

    // set a timeout: 
    [promise setTimeout:5.0]; 

    [promise.thenOn(dispatch_get_main_queue(), ^id(id result) { 
     XCTAssertTrue([result isEqualToString:@"OK"], @""); 
     XCTAssertTrue(... , @""); 
     return nil; 
    }, ^id(NSError* error) { 
     // this may fail either due to a timeout or when saveUser fails: 
     XCTFail(@"Method failed with error: %@", error); 
     return nil; 
    }) runLoopWait]; // suspend the run loop, until after the promise gets resolved 

} 

的几个注意事项:

可以执行的成功或失败处理程序(这是注册的PROMI与thenOn声明)任何队列,只需指定队列。这不会死锁主线程,即使处理程序在主线程上显式执行,并且测试也在主线程上运行(感谢运行循环机制)。

方法runLoopWait将进入运行循环,并且只有在承诺解决后才返回。这既有效又高效。

承诺接受超时。如果超时到期,承诺将被“拒绝”(已解决)并带有相应的错误。

IMO,承诺是处理常见异步编程问题的宝贵工具 - 不仅仅是单元测试的帮手。 (参见:维基文章Futures and promises

请注意,我是RXPromise library的作者,因此我完全失之偏颇。)

还有其他的承诺实现了Objective-C的为好。

+0

感谢你们,我看了一下,RXPromise看起来像一个非常有用的框架,如此好的工作。也许我没有完全解释自己;我不想直接调用saveUser:withBlock:方法,因为它会进行网络调用,这显然不适合测试。我有办法嘲笑这个吗?再次感谢。例如 –

+0

查看[OCMock](http://ocmock.org);) – CouchDeveloper

+0

不需要第三方库 - 您可以使用XCTestExpectation。教程可在NSHipster:http://nshipster.com/xctestcase/ –

1

原来,这只是由于我缺乏对单元测试的理解。对模仿对象,存根和类似的一些研究后,我想出了:

- (void)testSaveUser { 
    id userModelMock = [OCMockObject mockForClass:[UserModel class]]; 
    id userControllerMock = [OCMockObject partialMockForObject:self.userController]; 

    [[userModelMock expect] saveInBackgroundWithBlock:[OCMArg any]]; 
    [userControllerMock saveUser:userModelMock withBlock:[OCMArg any]]; 

    [userModelMock verify]; 
} 
+1

是的,它看起来像最初你没有调用你想测试的方法!我认为'self.userController'正在为每个测试返回由您的测试类创建的用户控制器对象。很高兴你正在取得进展。 –

+0

只是想说,在想要测试的方法中传递[OCMArg any]作为块是没有意义的。取决于你想测试什么,而是传递一个假的块或零 – e1985

1

随着https://github.com/hfossli/AGAsyncTestHelper/WAIT_WHILE(<expression>, <time_limit>)你可以写

- (void)testSaveUser 
{ 
    __block BOOL saved = NO; 
    [PFUser saveUser:userModelMock withBlock:^(BOOL success, NSError *error) { 
     saved = YES; 
    }]; 
    WAIT_WHILE(!saved, 10.0, @"Failed to save user within 10 seconds timeframe); 
}