2013-05-09 5 views
0

Я пытаюсь проверить, вызван ли метод класса с помощью OCMock. Я собрал с сайта OCMock и другие ответы на SO, что новая версия OCMock (2.1) добавляет поддержку методов класса stubbing.Метод класса тестирования с использованием выпуска OCMock 2.1.1

Я пытаюсь сделать то же самое:

DetailViewController:

+(BOOL)getBoolVal 
{ 
return YES; 
} 

Test Case:

-(void) testClassMethod 
{ 
id detailMock = [OCMockObject mockForClass:[DetailViewController class]]; 

[[[detailMock stub] andReturnValue:OCMOCK_VALUE((BOOL){YES})] getBoolVal:nil]; 
} 

тест работает, а также успех, но это удается, даже если я вернусь NO вместо YES от getBoolVal метода в DetailViewController. При сохранении точки останова в этом методе выполнение теста не прекращает указывать, что метод не вызывается.

Как я могу проверить метод класса?

ответ

0

EDIT

Просто более внимательно посмотрел на последнюю версию OCMock, и я думаю, что вы правы в интерпретации способ поиздеваться метод класса, который вы непосредственно тестирование, так что это вопрос просто, что вы называете это неправильной подписью?

Ответ ниже - общий случай (так что также полезно, когда тестируемый метод вызывает метод класса).

Оригинал

Проверка метода класса с OCMock немного сложнее. То, что вы сейчас делаете, это создание объекта , называемого detailMock, и обрезание экземпляра метод getBoolVal: (Кстати, ваш прототип метода не принимает аргумента, поэтому вы не должны пропускать нуль это - чтобы получить действительно nit-picky, если вы хотите следовать рекомендациям Apple, они не рекомендуют использовать слово get в getter (если вы не отправляете ссылку на указатель, чтобы получить set)). Компиляция не прерывается, потому что detailMock - это идентификатор и готов ответить на любой селектор.

Итак, как протестировать метод класса? В общем случае вам нужно будет немного подпрыгнуть. Вот как я это делаю.

Давайте посмотрим, как мы будем подделывать NSURLConnection, которые вы также сможете применить к своему классу.

Start, расширяя свой класс:

@interface FakeNSURLConnection : NSURLConnection 

+ (id)sharedInstance; 
+ (void)setSharedInstance:(id)sharedInstance; 
+ (void)enableMock:(id)mock; 
+ (void)disableMock; 

- (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate; 
@end 

Заметьте, что я заинтересован в тестировании connectionWithRequest: делегат и что я расширил класс для добавления общедоступного экземпляра метода с той же сигнатурой, что и класса. Давайте посмотрим на реализацию:

@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 

Так что же здесь происходит? Сначала есть некоторые макросы, о которых я расскажу ниже. Затем я переопределил метод класса, чтобы вызвать метод экземпляра. Мы можем использовать OCMock для моделирования методов экземпляра, поэтому, если метод класса вызовет метод экземпляра, мы можем вызвать метод класса, называемый mock.

Мы не хотим использовать FakeNSURLConnection в нашем реальном коде, но мы хотим использовать его в нашем тестировании. Как мы можем это сделать? Мы можем swizzle методы класса между NSURLConnection и FakeNSURLConnection. Это означает, что после того, как мы подпишем звонок NSURLConnection connectionWithRequest:delegate с вызовом FakeNSURLConnection connectionWithRequest:delegate. Это подводит нас к нашим макрокомандам:

#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; \ 
} 

Я бы рекомендовал что-то вроде этого, так что вы не случайно повторно Swizzle ваших методов класса. Итак, как бы вы это использовали?

id urlConnectionMock = [OCMockObject niceMockForClass:FakeNSURLConnection.class]; 
[FakeNSURLConnection enableMock:urlConnectionMock]; 
[_mocksToDisable addObject:FakeNSURLConnection.class]; 

[[[urlConnectionMock expect] andReturn:urlConnectionMock] connectionWithRequest:OCMOCK_ANY delegate:OCMOCK_ANY]; 

Это довольно много, это - вы swizzled методы, чтобы ваш фальшивый класс будет вызвана, и что будет вызывать издеваться.

Ах, но последнее. _mocksToDisable - это NSMutableArray, который будет содержать объект класса для каждого класса, который мы swizzled.

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

Мы делаем это в tearDown, чтобы убедиться, что мы unswizzled нашего класса после того, как тест запуска - не сделать это прямо в тесте, потому что, если есть исключение не все ваш тестовый код может получить казнен но tearDown всегда будет.

Могут быть другие макетные технологии, которые делают это более простым, хотя я нашел, что это не так плохо, так как вы пишете его один раз и можете использовать его много раз.

0

Может быть, я что-то здесь отсутствует (и я знаю, что это немного старый), но ваш класс подписи +(BOOL)getBoolVal { return YES; } и в тесте, вы вызываете ожидать на getBoolVal:nil

Те не совпадают, правильно? В этом случае ваш классный макет скажет: «О, это не подпись, которую я ожидаю», и я считаю, что попытаюсь передать ее классу underyling. См. forwardInvocationForClassObject в OCMock source.

Что касается того, почему вы получаете НЕТ (поскольку базовый класс также возвращает ДА, что делает этот тест спорным, но это еще одна проблема), я не уверен на 100%, но, возможно, это просто C -ism - 'неопределенное значение, void, 0 (false/NO) "

Смежные вопросы