OCMockでクラスメソッドをスタブする方法は?
-
05-07-2019 - |
質問
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カテゴリオブジェクトを考えることができますか?
解決
Rubyの世界から来た私は、あなたが達成しようとしていることを正確に理解しています。どうやら、今日とまったく同じことをしようとして文字通り3時間進んでいました(タイムゾーンのことですか?:-)。
とにかく、クラスメソッドをスタブ化することは文字通りクラスに到達する必要があり、いつ、どこで、そのメソッド実装を変更する必要があるため、OCMockで望まれる方法ではサポートされていないと信じますまたはメソッドを呼び出す人。これは、OCMockが行うように思われるものとは対照的です。OCMockは、「実際」の代わりに直接操作するプロキシオブジェクトを提供することです。指定されたクラスのオブジェクト。
たとえば、NSURLConnection + sendSynchronousRequest:returningResponse:error:メソッドをスタブすることは合理的なようです。ただし、コード内でのこの呼び出しの使用は多少埋もれているのが一般的であるため、それをパラメーター化してNSURLConnectionクラスのモックオブジェクトにスワップするのは非常に厄介です。
このため、「メソッドのスウィズリング」メソッドはあなたが発見したアプローチは、セクシーではありませんが、クラスメソッドをスタブするためにまさにやりたいことです。非常に面倒だと言うのは非常に面倒です。「違法」だということに同意するのはどうでしょうか。 OCMockが私たちに命を吹き込むほど便利ではないかもしれません。それにもかかわらず、それは問題のかなり簡潔な解決策です。
他のヒント
OCMock 3の更新
OCMockは、クラスメソッドスタブをサポートするための構文を近代化しました。
id classMock = OCMClassMock([SomeClass class]);
OCMStub(ClassMethod([classMock aMethod])).andReturn(aValue);
更新
OCMockは、クラスメソッドのスタブ化をすぐにサポートするようになりました。これで、OPのコードは投稿どおりに機能するはずです。クラスメソッドと同じ名前のインスタンスメソッドがある場合、構文は次のとおりです。
[[[[mock stub] classMethod] andReturn:aValue] aMethod]
OCMockの機能を参照してください。
オリジナルの回答
Barry Warkの回答に続くサンプルコード。
偽のクラス、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);
}
ここに、クラスメソッドのスウィズル実装を備えた素敵な「要点」があります。 https://gist.github.com / 314009
テスト対象のメソッドを変更して、 NSURLConnection
のクラスを注入するパラメーターを取得する場合、特定のセレクターに応答するモックを渡すのは比較的簡単です(作成する必要がある場合があります)インスタンスメソッドとしてセレクターを持ち、そのクラスをモックするテストモジュールのダミークラス)。このインジェクションがない場合、クラスメソッドを使用し、基本的に NSURLConnection
(クラス)をシングルトンとして使用しているため、シングルトンオブジェクトを使用するアンチパターンに陥り、コードのテスト容易性が損なわれます。 。
質問のブログ投稿へのリンクとRefuXの要点は、私が彼らのアイデアのブロック対応実装を思い付くよう促しました: https ://gist.github.com/1038034