如何在OCMock中存根类的方法?

我经常在我的iPhone Objective-Cunit testing中发现我想要将一个类方法(例如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). 

我真的很难find任何文档,但我认为OCMock不支持类方法。

我经过了很多Googlesearch之后发现了这个技巧。 它工作,但是非常麻烦: http : //thom.org.uk/2009/05/09/mocking-class-methods-in-objective-c/

无论如何要在OCMock内做到这一点? 或者,有人可以想到一个聪明的OCMock类别对象,可以写成这样的事情?

来自Ruby的世界,我明白你到底想要完成什么。 显然,你是在我之前三个小时试图做同样的事情今天(时区的东西?:-)。

无论如何,我相信这是不支持在OCMock期望的方式,因为桩类方法需要逐字地到达类和改变它的方法实现,无论何时何地调用方法。 这与OCMock似乎做的相反,它是为您提供一个代理对象,您可以直接操作并代替指定类的“真实”对象。

例如,想要存根NSURLConnection + sendSynchronousRequest:returningResponse:error:方法似乎是合理的。 然而,在我们的代码中使用这个调用通常是被掩盖的,因此使它参数化并交换NSURLConnection类的模拟对象变得非常尴尬。

出于这个原因,我认为你所发现的“方法调整”方法,虽然不够性感,但正是你想要做的是用来存储类的方法。 说这麻烦,看起来很极端 – 我们认为这是“不雅”,也许不像OCMock为我们生活一样方便。 不过,这是一个相当简洁的解决scheme。

更新OCMock 3

OCMock已经将支持类方法存根的语法现代化:

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

更新

现在,OCMock支持类方法存根(stub)。 OP的代码现在应该像发布一样工作。 如果存在与类方法同名的实例方法,则语法为:

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

请参阅OCMock的function 。

原始答复

示例代码如下Barry Wark的答案。

假的类,只是stubbing connectionWithRequest:委托:

 @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); } 

这里有一个不错的“要点”,用于类方法的debugging: https : //gist.github.com/314009

如果你修改你的testing方法来获取注入NSURLConnection类的参数,那么传递一个模拟对给定的select器做出响应是比较容易的(你可能必须在你的testing模块中创build一个虚拟类,select器作为一个实例方法,并模拟该类)。 没有这个注入,你使用了一个类方法,本质上使用NSURLConnection (类)作为一个单例,因此陷入了使用单例对象的反模式,并且代码的可testing性受到了影响。

在问题和RefuX的要点链接到博文的启发我想出了块实现他们的想法: https ://gist.github.com/1038034