2011-09-17 54 views
1

我試圖測試實例的MFMailComposeViewController實例的方法調用。正在測試的方法調用了幾種方法,包括setSubject:OCMock與方法類的從測試方法

我想測試SETSUBJECT發送一個特定的NSString,在這種情況下@「測試信息」。
無論我爲模擬存根中預期的字符串指定什麼都沒有失敗。

在單元測試類:

#import <OCMock/OCMock.h> 

- (void)testEmail { 
    TestClass *testInstance = [[TestClass alloc] init]; 

    id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]]; 
    [[mock stub] setSubject:@"Test Message"]; 

    [testInstance testMethod]; 
} 

在識別TestClass:

- (void)testMethod { 
    MFMailComposeViewController *mailComposeVC = [[MFMailComposeViewController alloc] init]; 
    [mailComposeVC setSubject:@"Bad Message"]; 
} 

Test Suite 'Email_Tests' started at 2011-09-17 18:12:21 +0000 
Test Case '-[Email_Tests testEmail]' started. 
Test Case '-[Email_Tests testEmail]' passed (0.041 seconds). 

測試應該失敗。

我在iOS模擬器中測試這個,並在設備上得到相同的結果。

我在做什麼錯?有什麼辦法可以做到這一點?

回答

5

您創建一個模擬,但從來沒有測試它傳遞給類,或要求模擬來驗證自身。你需要某種形式的依賴注入來說:「不要使用MFMailComposeViewController,而是使用我給你的這個其他東西。」

下面是做到這一點的方法之一。在測試中的類,而不是直接分配MFMailComposeViewController,得到它通過工廠方法,如:

@interface TestClass : NSObject 

- (void)testMethod; 

// Factory methods 
+ (id)mailComposeViewController; 

@end 

這裏的落實。你在泄漏,所以請注意,工廠方法返回一個自動釋放對象。

- (void)testMethod { 
    MFMailComposeViewController *mailComposeVC = 
            [[self class] mailComposeViewController]; 
    [mailComposeVC setSubject:@"Bad Message"]; 
} 

+ (id)mailComposeViewController { 
    return [[[MFMailComposeViewController alloc] init] autorelease]; 
} 

在在測試方面,我們創建了一個測試子類,覆蓋了工廠方法,因此它提供什麼,我們希望它:

@interface TestingTestClass : TestClass 
@property(nonatomic, assign) id mockMailComposeViewController; 
@end 

@implementation TestingTestClass 
@synthesize mockMailComposeViewController; 

+ (id)mailComposeViewController { 
    return mockMailComposeViewController; 
} 

@end 

現在,我們已經準備好進行測試。我做一些不同的事情:

  • 分配一個測試子類,而不是實際的類
  • 設置了一個期望的模擬,而不僅僅是一個存根
  • 注入(和不漏!)嘲笑進入測試子
  • 驗證模擬末

這裏的測試:

- (void) testEmail { 
    TestClass *testInstance = [[[TestClass alloc] init] autorelease]; 

    id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]]; 
    [[mock expect] setSubject:@"Test Message"]; 
    [testInstance setMockMailComposeViewController:mock]; 

    [testInstance testMethod]; 

    [mock verify]; 
} 

爲了完整起見,我們需要一個最後的測試,這是保證在實際的類工廠方法返回我們所期望的:

- (void)testMailComposerViewControllerShouldBeCorrectType { 
    STAssertTrue([[TestClass mailComposeViewController] 
       isKindOfClass:[MFMailComposeViewController class]], nil); 
} 
+0

感謝您的方法,並回答!我正在學習如何爲單元測試編寫單元測試和代碼。在這種情況下,我將單元測試添加到遺留代碼,所以我希望在對它進行修改之前不要進行任何修改或進行最低限度的修改。有一點,我嘗試傳遞一個'MFMailComposeViewController'的模擬,它爲通過測試工作,但導致崩潰的測試失敗,這是通常的OCMock?此外,爲了清晰起見,我故意忽略內存管理,儘管我傾向於在可能的情況下使用ARC編寫代碼。 – zaph

+0

OCMock通過引發異常來報告失敗。 SenTestingKit旨在捕獲和報告異常,但不幸的是,由於Simulator中的錯誤,它會崩潰。我正在研究一種避免這種情況的替代模擬框架,但它還沒有準備好。 –

2

喬恩·裏德的是一個合理的方法,但它似乎使mailComposeViewController一個類方法使其複雜化。並且在你的測試代碼中繼承它意味着你在測試的時候總會得到模擬版本,這可能不是你想要的。我會讓它成爲一個實例方法。然後你可以使用部分模擬在測試時將其覆蓋:

-(void) testEmail { 
    TestClass *testInstance = [[[TestClass alloc] init] autorelease]; 

    id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]]; 
    [[mock expect] setSubject:@"Test Message"]; 
    id mockInstance = [OCMockObject partialMockForObject:testInstance]; 
    [[[mockInstance stub] andReturn:mock] mailComposeViewController]; 

    [testInstance testMethod]; 

    [mock verify]; 
} 

如果你把它作爲一個類的方法,你可能會考慮使其成爲一個靜態全局和暴露的方式來覆蓋它:

static MFMailComposeViewController *mailComposeViewController = nil; 

-(id)mailComposeViewController { 
    if (!mailComposeViewController) { 
     mailComposeViewController = [[MFMailComposeViewController alloc] init]; 
    } 
    return mailComposeViewController; 
} 

-(void)setMailComposeViewController:(MFMailComposeViewController *)controller { 
    mailComposeViewController = controller; 
} 

然後,你的測試將類似於喬恩的例子:

-(void)testEmail { 
    TestClass *testInstance = [[[TestClass alloc] init] autorelease]; 

    id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]]; 
    [[mock expect] setSubject:@"Test Message"]; 
    [testInstance setMailComposeViewController:mock]; 

    [testInstance testMethod]; 

    [mock verify]; 

    // clean up 
    [testInstance setMailComposeViewController:nil]; 
} 
+0

部分嘲諷的好建議! –