使用OCMock版本2.1.1testing类的方法

我正在尝试检查是否使用OCMock调用类方法。 我从OCMock网站和其他答案收集到的新的OCMock版本(2.1)增加了对桩类方法的支持。

我正在尝试做同样的事情:

DetailViewController:

+(BOOL)getBoolVal { return YES; } 

testing用例:

 -(void) testClassMethod { id detailMock = [OCMockObject mockForClass:[DetailViewController class]]; [[[detailMock stub] andReturnValue:OCMOCK_VALUE((BOOL){YES})] getBoolVal:nil]; } 

testing正在运行,并且也成功,但即使从DetailViewController getBoolVal方法返回NO而不是YES,它也会成功。 在保持该方法的断点时,testing执行不会停止指示该方法未被调用。

那么我如何检查一个类的方法呢?

编辑

只是更仔细地观察了最新版本的OCMock,我认为你在解释你正在testing的类方法的方法是正确的,所以只是你用错误的签名来调用它的问题?

下面的答案是一般情况(当一个被测方法调用类方法时也是如此)。

原版的

使用OCMock检查类方法有点棘手。 你现在正在做的是创build一个叫做detailMock的模拟对象,并存储一个名为getBoolVal的实例方法:(顺便说一下,你的方法原型不会接受一个参数,所以你不应该通过零来得到真正的挑剔,如果你想遵循苹果的指导方针,他们build议不要在getter中使用“get”一词(除非你发送一个指针引用来获取set))。 编译不会失败,因为detailMock是一个id,并愿意响应任何select器。

那么如何testing一个Class方法? 一般情况下,你需要做一些调整。 这是我怎么做的。

让我们看看我们如何伪造NSURLConnection ,你也应该能够应用到你的课堂上。

从扩展你的课程开始:

 @interface FakeNSURLConnection : NSURLConnection + (id)sharedInstance; + (void)setSharedInstance:(id)sharedInstance; + (void)enableMock:(id)mock; + (void)disableMock; - (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate; @end 

请注意,我对testingconnectionWithRequest:delegate感兴趣,并且已经扩展该类以添加与类方法具有相同签名的公共实例方法。 我们来看看实现:

 @implementation FakeNSURLConnection SHARED_INSTANCE_IMPL(FakeNSURLConnection); SWAP_METHODS_IMPL(NSURLConnection, FakeNSURLConnection); DISABLE_MOCK_IMPL(FakeNSURLConnection); ENABLE_MOCK_IMPL(FakeNSURLConnection); + (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 

那么这里发生了什么? 首先有一些macros,我将在下面讨论。 接下来,我已经重写了类方法来调用实例方法。 我们可以使用OCMock来模拟实例方法,所以通过类方法调用实例方法,我们可以让类方法调用模拟。

尽pipe我们不想在我们的真实代码中使用FakeNSURLConnection,但我们确实希望在我们的testing中使用它。 我们应该怎么做? 我们可以调用NSURLConnectionFakeNSURLConnection之间的类方法。 这意味着,我们调用NSURLConnection connectionWithRequest:delegate调用FakeNSURLConnection connectionWithRequest:delegate 。 这让我们看到了我们的macros观:

 #define SWAP_METHODS_IMPL(REAL, FAKE) \ + (void)swapMethods \ { \ Method original, mock; \ unsigned int count; \ Method *methodList = class_copyMethodList(object_getClass(REAL.class), &count); \ for (int i = 0; i < count; i++) \ { \ original = class_getClassMethod(REAL.class, method_getName(methodList[i])); \ mock = class_getClassMethod(FAKE.class, method_getName(methodList[i])); \ method_exchangeImplementations(original, mock); \ } \ free(methodList); \ } #define DISABLE_MOCK_IMPL(FAKE) \ + (void)disableMock \ { \ if (_mockEnabled) \ { \ [FAKE swapMethods]; \ _mockEnabled = NO; \ } \ } #define ENABLE_MOCK_IMPL(FAKE) \ static BOOL _mockEnabled = NO; \ + (void)enableMock:(id)mockObject; \ { \ if (!_mockEnabled) \ { \ [FAKE setSharedInstance:mockObject]; \ [FAKE swapMethods]; \ _mockEnabled = YES; \ } \ else \ { \ [FAKE disableMock]; \ [FAKE enableMock:mockObject]; \ } \ } #define SHARED_INSTANCE_IMPL() \ + (id)sharedInstance \ { \ return _sharedInstance; \ } #define SET_SHARED_INSTANCE_IMPL() \ + (void)setSharedInstance:(id)sharedInstance \ { \ _sharedInstance = sharedInstance; \ } 

我build议这样的事情,所以你不会不小心重新调整你的课堂方法。 那么你将如何使用这个?

 id urlConnectionMock = [OCMockObject niceMockForClass:FakeNSURLConnection.class]; [FakeNSURLConnection enableMock:urlConnectionMock]; [_mocksToDisable addObject:FakeNSURLConnection.class]; [[[urlConnectionMock expect] andReturn:urlConnectionMock] connectionWithRequest:OCMOCK_ANY delegate:OCMOCK_ANY]; 

这几乎是 – 你已经调整了方法,所以你的假类将被召唤,这将叫你的模拟。

啊,但最后一件事。 _mocksToDisable是一个NSMutableArray ,它将包含我们调用的每个类的类对象。

 - (void)tearDown { for (id mockToDisable in _mocksToDisable) { [mockToDisable disableMock]; } } 

我们在tearDown做了这个操作,以确保在testing运行后我们的课程没有问题 – 不要在testing中正确使用它,因为如果有一个例外,那么不是所有的testing代码都可以被执行,但是tearDown总是会被执行。

也许有其他的模拟技术可以使这个更简单,但是我发现它并没有那么糟糕,因为你只写了一次,可以多次使用它。

也许我在这里错过了一些东西(我知道这+(BOOL)getBoolVal { return YES; } ),但是你的类签名是+(BOOL)getBoolVal { return YES; } +(BOOL)getBoolVal { return YES; }在你的testing中,你正在调用getBoolVal:nil

那些不匹配,对吧? 在这种情况下,你的阶级模拟会说:“噢,这不是我所期望的签名”,我相信,试着把它传递给解职class。 请参阅OCMock源代码中的forwardInvocationForClassObject

至于为什么你会得到NO(因为底层类也返回YES,这使得这种testingtypes没有意义,但这是另一个问题),我不是100%确定,但也许它只是一个C – 主义 – 'indeterminate value,void,0(false / NO)“