2014-02-14 132 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); 
}