2011-09-17 24 views
1

MFMailComposeViewControllerのインスタンスをインスタンス化するメソッドをテストしようとしています。テストされるメソッドは、MFMailComposeViewControllerのいくつかのメソッドを呼びます(setSubject:を含む)。テストメソッドから呼び出されたメソッドを持つクラスのOCMock

この場合、setSubjectに特定のNSString(この場合は@ Test Message)が送信されていることをテストします。
モックスタブの期待される文字列に何を指定しても、失敗はありません。

ユニットテストクラスで:TestClassをでは

#import <OCMock/OCMock.h> 

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

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

    [testInstance testMethod]; 
} 

- (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を使用する代わりに、私があなたに与えているこの他のものを使用してください。

これを行う方法が1つあります。テスト対象のクラスでは、代わりに直接MFMailComposeViewControllerを確保するので、このように、ファクトリメソッドを通じてそれを得る:

@interface TestClass : NSObject 

- (void)testMethod; 

// Factory methods 
+ (id)mailComposeViewController; 

@end 

はここでの実装です。漏れていたので、ファクトリメソッドはオートレリースされたオブジェクトを返します。

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

@implementation TestingTestClass 
@synthesize mockMailComposeViewController; 

+ (id)mailComposeViewController { 
    return mockMailComposeViewController; 
} 

@end 

は、今、私たちはテストの準備ができている:テスト側の

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

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

以上は、我々はそれが我々がそれを好きな提供してファクトリメソッドをオーバーライドするテストサブクラスを作成します。私は違ったいくつかのことを行います。

  • は、実際のクラスではなく、テストのサブクラスを割り当て
  • は期待してモックをセットアップするだけではなく、スタブ
  • 注入します(と漏れません!)テストのサブクラスにモック
  • が終わり

をモックを確認してくださいここではテストです:

- (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]; 
} 
完全のために

、我々は1つの最終テストを必要とし、それが実際のクラスのファクトリメソッドは、我々が期待するものを返すことを保証します:mailComposeViewController Aを作っているようですけれども

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

ありがとう!私はユニットテスト容易性のためにテストとコードを書く方法を学んでいます。このケースでは、レガシーコードにユニットテストを追加しています。そのため、私がそれらを実装した後まで、ユニットテストを変更する必要はありません。 1点、私は 'MFMailComposeViewController'のモックを渡そうとしましたが、合格テストでは動作しましたが、失敗したテストではクラッシュしました。これは通常のOCMockですか?また、可能な場合にはARCでコードを書く傾向がありますが、わかりやすくするために私は意図的にメモリ管理を省略しました。 – zaph

+0

OCMockは例外を発生させて失敗を報告します。 SenTestingKitは例外をキャッチして報告するように設計されていますが、残念なことにSimulatorのバグにより、代わりにクラッシュします。私はこれを避ける代替の模擬フレームワークに取り組んでいますが、まだ準備はできていません。 –

2

は、ジョン・リードのは、合理的なアプローチでありますクラスメソッドはそれを複雑にします。テストコードでサブクラス化すると、テスト時に擬似バージョンが得られることになります。これはあなたが望むものではない可能性があります。私はそれをインスタンスメソッドにします。その後、テスト時間でそれをオーバーライドする部分モックを使用することができます。

-(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; 
} 

次に、あなたのテストは、Jonの例のようになります。あなたのアプローチと答えを

-(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

部分的嘲笑のアドバイス! –

関連する問題