objective-c 如何在 OCMock 中存根类方法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1810053/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to stub a class method in OCMock?
提问by Jeremy
I often find in my iPhone Objective-C unit tests that I want stub out a class method, e.g. NSUrlConnection's +sendSynchronousRequest:returningResponse:error: method.
我经常在我的 iPhone Objective-C 单元测试中发现我想要存根一个类方法,例如 NSUrlConnection 的 +sendSynchronousRequest:returningResponse:error: 方法。
Simplified example:
简化示例:
- (void)testClassMock
{
id mock = [OCMockObject mockForClass:[NSURLConnection class]];
[[[mock stub] andReturn:nil] sendSynchronousRequest:nil returningResponse:nil error:nil];
}
When running this, I get:
运行此程序时,我得到:
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).
I've had a really hard time finding any documentation on this, but I assume that class methods aren't supported by OCMock.
我很难找到任何关于此的文档,但我认为 OCMock 不支持类方法。
I found this tip after a lot of Googling. It works, but is very cumbersome: http://thom.org.uk/2009/05/09/mocking-class-methods-in-objective-c/
经过大量谷歌搜索后,我发现了这个提示。它有效,但非常麻烦:http: //thom.org.uk/2009/05/09/mocking-class-methods-in-objective-c/
Is there anyway to do this within OCMock? Or can someone think of a clever OCMock category object that could be written to accomplish this sort of thing?
无论如何在OCMock中执行此操作?或者有人能想到一个聪明的 OCMock 类别对象来完成这种事情吗?
采纳答案by mharper
Coming from the world of Ruby, I understand exactly what you're trying to accomplish. Apparently, you were literally three hours ahead of me trying to do exactly the same thing today (time zone thing? :-).
来自 Ruby 的世界,我完全理解你想要实现的目标。显然,您实际上比我今天尝试做同样的事情早了三个小时(时区问题?:-)。
Anyway, I believethat this is not supported in the way one would desire in OCMock because stubbing a class method needs to literally reach into the class and changes its method implementation regardless of when, where, or who calls the method. This is in contrast to what OCMock seems to do which is to provide you a proxy object that you manipulate and otherwise operate on directly and in lieu of a "real" object of the specified class.
无论如何,我相信这在 OCMock 中并不受人们所期望的支持,因为存根类方法需要真正进入类并更改其方法实现,而不管何时、何地或谁调用该方法。这与 OCMock 似乎所做的相反,它为您提供一个代理对象,您可以直接操作和以其他方式操作并代替指定类的“真实”对象。
For example, it seems reasonable to want to stub NSURLConnection +sendSynchronousRequest:returningResponse:error: method. However, it is typical that the use of this call within our code is somewhat buried, thus making it very awkward to parameterize it and swap in a mock object for the NSURLConnection class.
例如,想要存根 NSURLConnection +sendSynchronousRequest:returningResponse:error: 方法似乎是合理的。然而,在我们的代码中使用这个调用是很典型的,这使得参数化它并为 NSURLConnection 类交换一个模拟对象变得非常尴尬。
For this reason, I think the "method swizzling" approach you've discovered, while not sexy, is exactly what you want to do for stubbing class methods. To say it's verycumbersome seems extreme -- how about we agree it's "inelegant" and maybe not as convenient as OCMock makes life for us. Nevertheless, it's a pretty concise solution to the problem.
出于这个原因,我认为您发现的“方法混合”方法虽然不性感,但正是您想要为类方法做的事情。说它非常繁琐似乎是极端的——我们同意它“不雅”,也许不如 OCMock 为我们创造生活那么方便。尽管如此,这是一个非常简洁的问题解决方案。
回答by Ben Flynn
Update for OCMock 3
OCMock 3 更新
OCMock has modernized its syntax for supporting class method stubbing:
OCMock 对其语法进行了现代化,以支持类方法存根:
id classMock = OCMClassMock([SomeClass class]);
OCMStub(ClassMethod([classMock aMethod])).andReturn(aValue);
Update
更新
OCMock now supports class method stubbing out of the box. The OP's code should now work as posted. If there is an instance method with the same name as the class method, the syntax is:
OCMock 现在支持开箱即用的类方法存根。OP 的代码现在应该可以正常工作了。如果存在与类方法同名的实例方法,则语法为:
[[[[mock stub] classMethod] andReturn:aValue] aMethod]
See OCMock's Features.
请参阅OCMock 的功能。
Original Answer
原答案
Sample code following Barry Wark's answer.
Barry Wark 回答后的示例代码。
The fake class, just stubbing connectionWithRequest:delegate:
假类,只是存根 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
Switching to and from the mock:
切换到和从模拟:
{
...
// 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);
}
回答by RefuX
Here is a nice 'gist' with a swizzle implementation for class methods: https://gist.github.com/314009
这是一个很好的“要点”,带有类方法的 swizzle 实现:https: //gist.github.com/314009
回答by Barry Wark
If you modify your method under test to take a parameter which injects the class of the NSURLConnection, then it's relatively easy to pass in a mock that responds to the given selector (you may have to create a dummy class in your test module which has the selector as an instance method and mock that class). Without this injection, you're using a class method, essentially using NSURLConnection(the class) as a singleton and hence have fallen into the anti-pattern of using singleton objects and the testability of your code has suffered.
如果您修改测试中的方法以采用注入 类的参数NSURLConnection,那么传入响应给定选择器的模拟相对容易(您可能必须在具有选择器的测试模块中创建一个虚拟类作为实例方法并模拟该类)。如果没有这种注入,您将使用类方法,本质上将NSURLConnection(类)用作单例,因此陷入了使用单例对象的反模式,并且您的代码的可测试性受到了影响。
回答by Pavel Kunc
Link to the blogpost in the question and RefuX gist inspired me to come up with block enabled implementation of their ideas: https://gist.github.com/1038034
链接到问题中的博客文章和 RefuX gist 启发我想出他们的想法的块启用实现:https://gist.github.com/1038034

