11

我該如何測試我的NSURLConnection委託? 我做了一個ConnectionDelegate類,它符合不同的協議,以將數據從Web提供給不同的ViewController。在我變得太遠之前,我想開始編寫我的單元測試。 但我不知道如何測試他們作爲一個單位沒有互聯網連接。我也想我應該做什麼來處理異步回調。如何單元測試一個NSURLConnection委託?

回答

15

雖然這與Jon的迴應類似,但無法將其納入評論。第一步是確保你沒有創建一個真正的連接。實現此目的的最簡單方法是將連接的創建拉入工廠方法,然後在測試中替換工廠方法。有了OCMock的部分模擬支持,這可能看起來像這樣。

在你真正的類:

- (NSURLConnection *)newAsynchronousRequest:(NSURLRequest *)request 
{ 
    return [[NSURLConnection alloc] initWithRequest:request delegate:self]; 
} 

在您的測試:

id objectUnderTest = /* create your object */ 
id partialMock = [OCMockObject partialMockForObject:objectUnderTest]; 
NSURLConnection *dummyUrlConnection = [[NSURLConnection alloc] 
    initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"file:foo"]] 
    delegate:nil startImmediately:NO]; 
[[[partialMock stub] andReturn:dummyUrlConnection] newAsynchronousRequest:[OCMArg any]]; 

現在,當被測你的對象嘗試創建它實際上得到測試所創建的虛擬連接的URL連接。虛擬連接不必是有效的,因爲我們沒有啓動它,它永遠不會被使用。如果你的代碼使用連接,你可以返回另一個模擬,一個嘲笑NSURLConnection。

第二步是調用觸發NSURLConnection的創建你的對象上的方法:由於被測對象沒有使用真正的方面,我們現在可以調用從委託方法

[objectUnderTest doRequest]; 

測試。

int statusCode = 200; 
id responseMock = [OCMockObject mockForClass:[NSHTTPURLResponse class]]; 
[[[responseMock stub] andReturnValue:OCMOCK_VALUE(statusCode)] statusCode]; 
[objectUnderTest connection:dummyUrlConnection didReceiveResponse:responseMock]; 

NSData *responseData = [RESPONSE_TEXT dataUsingEncoding:NSASCIIStringEncoding]; 
[objectUnderTest connection:dummyUrlConnection didReceiveData:responseData]; 

[objectUnderTest connectionDidFinishLoading:dummyUrlConnection]; 

就是這樣:對於我們使用另一種模擬的NSURLResponse,響應數據是從已定義的其他地方測試中的字符串創建。您已經有效地僞造了測試對象與連接之間的所有交互,現在您可以檢查它是否處於應該處於的狀態。

如果您想查看一些「真實」代碼,查看使用NSURLConnections的CCMenu項目中的類的測試。這有點令人困惑,因爲被測試的類也被命名爲連接。

http://ccmenu.svn.sourceforge.net/viewvc/ccmenu/trunk/CCMenuTests/Classes/CCMConnectionTest.m?revision=129&view=markup

+0

這非常有用。我試圖擴展它來覆蓋來回的對話,而不是一次性的請求/響應場景,但我沒有太多的運氣。我發佈了一個單獨的問題:http://stackoverflow.com/questions/16472878/how-can-you-use-ocmock-with-nsurlconnection-delegate-for-a-series-of-mock-networ – Gruntcakes 2013-05-09 23:34:19

-1

這是我做的:

  1. 獲取XAMPP控制面板 http://www.apachefriends.org/en/xampp.html
  2. 啓動Apache服務器
  3. 在你~/Sites文件夾,把一個測試文件(任何你想要的數據,我們會打電話給它my file.test)。
  4. 使用URL啓動您的代理http://localhost/~username/myfile.test
  5. 停止Apache服務器時,不使用它。
+1

這限制在404,200和無響應的響應。如果你想測試,衝突409,204,403和其他狀態代碼,etags,壓縮等等...... 另外,如果你使用自動構建服務器,你打算安裝apache和這個每種測試機器都有哪些東西? – Guillermo 2012-07-17 11:23:20

+1

不是一種真正應該作爲推薦被傳遞的方法。吉列爾莫在他的評論中涵蓋了一些很好的理由。我認爲我們應該鼓勵更好的編程實踐,而不是開發人員的回答...... – Rob 2013-12-16 16:12:47

5

我在單元測試中避免了聯網。相反:

  • 我在方法中分離NSURLConnection。
  • 我創建了一個測試子類,覆蓋該方法以刪除NSURLConnection的所有痕跡。
  • 我寫了一個測試,以確保所需的方法在需要時被調用。然後我知道它會在現實生活中引發NSURLConnection。

然後我專注於更有趣的部分:合成具有各種特徵的模擬NSURLResponses,並將它們傳遞給NSURLConnectionDelegate方法。

+0

在我的委託方法中嘲笑或存根NSURLConnection怎麼樣?我將NSURLConnection分類爲包含接收到的NSData,接收者(我將寫入的JSON獲取者)以及用於調用它的請求。 – Moxy 2012-03-29 06:36:49

+0

看完我的代碼並再次閱讀您的答案後,我發現我正處在正確的軌道上! – Moxy 2012-03-29 07:35:28

+0

我的NSURLConnection已經被隔離在一個方法 - (void)sendRequest:(NSURLRequest *)請求forReceiver:(id )接收方。我可以測試所有其他方法以確保URL和請求格式正確。另一方面,通過重寫sendRequest方法,我可以傳遞僞數據來測試我的接收器。這有意義嗎? – Moxy 2012-03-29 07:44:14

5

我最喜歡的做法是將NSURLProtocol子類化,然後讓它響應所有http請求 - 或者其他協議。然後,您在-setup方法中註冊測試協議,並將其註銷到-tearDown方法中。 然後,您可以使用此測試協議將一些衆所周知的數據提供回您的代碼,以便您可以在單元測試中驗證它。

我寫了一些關於這個問題的博客文章。與您的問題最相關的可能是Using NSURLProtocol for Injecting Test DataUnit Testing Asynchronous Network Access

您可能還想看看我的以前的文章中描述的ILCannedURLProtocol。來源可在Github

+0

我讀了兩篇文章,他們非常有趣。我實際上開始就如何解決這個問題感謝他們。我不認爲我需要子類化NSURLProtocol(並且我更喜歡在未來會更改它的情況下不這樣做),因爲我的連接委託不處理數據。我正在使用我的接收器作爲觀察者。所以通過重寫開始連接的方法,我可以提供我想要的數據。 PS:偉大的博客!我已加入IL;) – Moxy 2012-03-29 11:52:58

+0

感謝您的意見。只是爲了澄清,ILCannedURLProtocol並不打算找到生產代碼的方式,所以我不會太擔心底層NSURLProtocol的變化。如果發生這種情況,那只是對您的測試設置的改變。 – 2012-03-29 11:58:38

+0

[MockNSURLConnection](https://github.com/wfleming/MockNSURLConnection)與您的方法非常吻合。 – snez 2012-12-20 14:22:21

9

EDIT(2014年2月18日):我只是碰到這種文章跌跌撞撞用更優雅的解決方案。

http://www.infinite-loop.dk/blog/2011/04/unittesting-asynchronous-network-access/

從本質上講,你有以下方法:

- (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs { 
    NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs]; 

    do { 
     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate]; 
     if([timeoutDate timeIntervalSinceNow] < 0.0) 
      break; 
    } while (!done); 

    return done; 
} 

在您的測試方法的末尾,你要確保事情還沒有超時:

STAssertTrue([self waitForCompletion:5.0], @"Timeout"); 

基本格式:

- (void)testAsync 
{ 
    // 1. Call method which executes something asynchronously 
    [obj doAsyncOnSuccess:^(id result) { 
     STAssertNotNil(result); 
     done = YES; 
    } 
    onError:^(NSError *error) [ 
     STFail(); 
     done = YES; 
    } 

    // 2. Determine timeout 
    STAssertTrue([self waitForCompletion:5.0], @"Timeout"); 
}  

==============

我遲到了,但我遇到了一個非常簡單的解決方案。 (非常感謝http://www.cocoabuilder.com/archive/xcode/247124-asynchronous-unit-testing.html

h文件:

@property (nonatomic) BOOL isDone; 

.m文件:

- (void)testAsynchronousMethod 
{ 
    // 1. call method which executes something asynchronously. 

    // 2. let the run loop do its thing and wait until self.isDone == YES 
    self.isDone = NO; 
    NSDate *untilDate; 
    while (!self.isDone) 
    { 
     untilDate = [NSDate dateWithTimeIntervalSinceNow:1.0] 
     [[NSRunLoop currentRunLoop] runUntilDate:untilDate]; 
     NSLog(@"Polling..."); 
    } 

    // 3. test what you want to test 
} 

isDone在所述異步方法執行線程設置爲YES

因此,在這種情況下,我在步驟1創建並啓動NSURLConnection,並將其委託給此測試類。在

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response

我設置self.isDone = YES;。我們跳出while循環並執行測試。完成。

+0

太棒了。給我足夠的東西,使東西工作:)謝謝。 – Johan 2013-12-24 13:36:53