Как заглушить метод класса в OCMock? - PullRequest
36 голосов
/ 27 ноября 2009

Я часто нахожу в своих модульных тестах iPhone Objective-C, что я хочу отключить метод класса, например, NSUrlConnection's + sendSynchronousRequest: returningResponse: error: method.

Упрощенный пример:

- (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.

Я нашел этот совет после большого количества Google. Это работает, но очень громоздко: http://thom.org.uk/2009/05/09/mocking-class-methods-in-objective-c/

Есть ли способ сделать это в OCMock? Или кто-то может придумать умный объект категории OCMock, который может быть написан для выполнения подобных задач?

Ответы [ 5 ]

45 голосов
/ 05 декабря 2012

Обновление для OCMock 3

OCMock модернизировал свой синтаксис для поддержки заглушки метода класса:

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

Обновление

OCMock теперь поддерживает окурки метода класса из коробки. Код ОП теперь должен работать как опубликовано. Если существует метод экземпляра с тем же именем, что и метод класса, синтаксис:

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

См. Особенности OCMock .

Оригинальный ответ

Пример кода после ответа Барри Уорка.

Ложный класс, просто заглушка соединения с запросом: делегат:

@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);
}
17 голосов
/ 28 ноября 2009

Исходя из мира Ruby, я точно понимаю, чего вы пытаетесь достичь. По-видимому, вы буквально на три часа опередили меня, пытаясь сделать то же самое сегодня (вещь часового пояса?: -).

В любом случае, я верю , что это не поддерживается так, как хотелось бы в OCMock, потому что заглушка метода класса должна буквально достигать класса и изменять реализацию его метода независимо от того, когда, где или кто вызывает метод. Это в отличие от того, что OCMock, по-видимому, делает, чтобы предоставить вам прокси-объект, которым вы манипулируете и иным образом оперируете напрямую, а не вместо «реального» объекта указанного класса.

Например, кажется разумным хотеть заглушить NSURLConnection + sendSynchronousRequest: returningResponse: error: метод. Однако, типично, что использование этого вызова в нашем коде несколько спрятано, что делает его очень неудобным для параметризации и подмены в фиктивном объекте для класса NSURLConnection.

По этой причине, я думаю, что обнаруженный вами метод "извращения методов", хотя и не сексуальный, это именно то, что вы хотите сделать для создания заглушек методов класса. Сказать, что это очень громоздко, кажется крайним - как насчет того, что мы согласны, что это "не элегантно" и, возможно, не так удобно, как OCMock создает для нас жизнь. Тем не менее, это довольно краткое решение проблемы.

6 голосов
/ 21 февраля 2011

Вот хороший 'гист' с реализацией swizzle для методов класса: https://gist.github.com/314009

4 голосов
/ 28 ноября 2009

Если вы модифицируете тестируемый метод так, чтобы он принимал параметр, который внедряет класс NSURLConnection, тогда относительно легко передать макет, который отвечает на данный селектор (возможно, вам придется создать фиктивный класс тестовый модуль, у которого есть селектор в качестве метода экземпляра и макет этого класса). Без этой инъекции вы используете метод класса, по существу используя NSURLConnection (класс) в качестве одиночного и, следовательно, попали в анти-шаблон использования одноэлементных объектов, и тестируемость вашего кода пострадала.

2 голосов
/ 21 июня 2011

Ссылка на пост блога в вопросе и суть RefuX вдохновили меня на создание блочной реализации их идей: https://gist.github.com/1038034

...