web-dev-qa-db-ja.com

OCMockでクラスメソッドをスタブする方法は?

IPhoneのObjective-Cユニットテストで、クラスメソッドをスタブアウトしたいことがよくあります。 NSUrlConnectionの+ sendSynchronousRequest:returningResponse:error:メソッド。

簡略化した例:

- (void)testClassMock
{
    id mock = [OCMockObject mockForClass:[NSURLConnection class]];
    [[[mock stub] andReturn:nil] sendSynchronousRequest:nil returningResponse:nil error:nil];
}

これを実行すると、次のようになります。

Test Case '-[WorklistTest testClassMock]' started.
Unknown.m:0: error: -[WorklistTest testClassMock] : *** -[NSProxy doesNotRecognizeSelector:sendSynchronousRequest:returningResponse:error:] called!
Test Case '-[WorklistTest testClassMock]' failed (0.000 seconds).

これに関するドキュメントを見つけるのに非常に苦労しましたが、クラスメソッドはOCMockでサポートされていないと思います。

グーグルをたくさんした後、このヒントを見つけました。それは機能しますが、非常に面倒です: http://thom.org.uk/2009/05/09/mocking-class-methods-in-objective-c/

OCMock内でこれを行う方法はありますか?または、誰かがこの種のことを達成するために書くことができる巧妙なOCMockカテゴリオブジェクトを考えることができますか?

36
Jeremy

Rubyの世界から来て、私はあなたが達成しようとしていることを正確に理解しています。どうやら、あなたは文字通り私より3時間早く、今日まったく同じことをしようとしていました(タイムゾーンのこと?:-)。

とにかく、私はbelieveこれは、OCMockで望む方法ではサポートされていません。これは、クラスメソッドをスタブ化すると、文字通りクラスに到達し、いつ、どこで、誰が呼び出すかに関係なく、メソッドの実装が変更されるためです。メソッド。これは、指定されたクラスの「実際の」オブジェクトの代わりに、直接操作または操作するプロキシオブジェクトを提供するというOCMockのように見えることとは対照的です。

たとえば、NSURLConnection + sendSynchronousRequest:returningResponse:error:メソッドをスタブ化するのは合理的と思われます。ただし、コード内でのこの呼び出しの使用はやや埋もれているのが一般的であるため、パラメーター化してNSURLConnectionクラスのモックオブジェクトにスワップするのは非常に厄介です。

このため、あなたが発見した「メソッドスウィズリング」アプローチは、セクシーではありませんが、クラスメソッドをスタブ化するためにまさにやりたいことだと思います。それが非常に面倒だと言うのは極端に思えます-それが「不法」であり、OCMockが私たちの生活を送るほど便利ではないことに同意するのはどうですか。それにもかかわらず、それは問題に対するかなり簡潔な解決策です。

17
mharper

OCMock 3の更新

OCMockは、クラスメソッドスタブをサポートするための構文を最新化しました。

id classMock = OCMClassMock([SomeClass class]);
OCMStub(ClassMethod([classMock aMethod])).andReturn(aValue);

更新

OCMockは、すぐに使用できるクラスメソッドスタブをサポートするようになりました。 OPのコードは投稿どおりに機能するはずです。クラスメソッドと同じ名前のインスタンスメソッドがある場合、構文は次のとおりです。

[[[[mock stub] classMethod] andReturn:aValue] aMethod]

OCMockの機能 を参照してください。

元の回答

BarryWarkの回答に続くサンプルコード。

ConnectionWithRequest:delegate:をスタブするだけの偽のクラス

@interface FakeNSURLConnection : NSURLConnection
+ (id)sharedInstance;
+ (void)setSharedInstance:(id)sharedInstance;
+ (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate;
- (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate;
@end
@implementation FakeNSURLConnection
static id _sharedInstance;
+ (id)sharedInstance { if (!_sharedInstance) { _sharedInstance = [self init]; } return _sharedInstance; }
+ (void)setSharedInstance:(id)sharedInstance { _sharedInstance = sharedInstance; }
+ (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate {
    return [FakeNSURLConnection.sharedInstance connectionWithRequest:request delegate:delegate];
}
- (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate { return nil; }
@end

モックへの切り替え:モックからの切り替え:

{
    ...
    // Create the mock and swap it in
    id nsurlConnectionMock = [OCMockObject niceMockForClass:FakeNSURLConnection.class];
    [FakeNSURLConnection setSharedInstance:nsurlConnectionMock];
    Method urlOriginalMethod = class_getClassMethod(NSURLConnection.class, @selector(connectionWithRequest:delegate:));
    Method urlNewMethod = class_getClassMethod(FakeNSURLConnection.class, @selector(connectionWithRequest:delegate:));
    method_exchangeImplementations(urlOriginalMethod, urlNewMethod);

    [[nsurlConnectionMock expect] connectionWithRequest:OCMOCK_ANY delegate:OCMOCK_ANY];

    ...
    // Make the call which will do the connectionWithRequest:delegate call
    ...

    // Verify
    [nsurlConnectionMock verify];

    // Unmock
    method_exchangeImplementations(urlNewMethod, urlOriginalMethod);
}
46
Ben Flynn

これは、クラスメソッドのスウィズル実装を備えた素敵な「要点」です: https://Gist.github.com/314009

6
RefuX

テスト対象のメソッドを変更して、NSURLConnectionのクラスを注入するパラメーターを取得する場合、指定されたセレクターに応答するモックを渡すのは比較的簡単です(ダミークラスを作成する必要がある場合があります)。インスタンスメソッドとしてセレクターを持ち、そのクラスをモックするテストモジュール)。このインジェクションがないと、クラスメソッドを使用します。基本的にはNSURLConnection(クラス)をシングルトンとして使用するため、シングルトンオブジェクトを使用するアンチパターンに陥り、コードのテスト容易性が低下します。

4
Barry Wark

質問のブログ投稿へのリンクとRefluXGistは、ブロック対応のアイデアの実装を思い付くように私を刺激しました: https://Gist.github.com/1038034

2
Pavel Kunc