Вопрос

Я часто нахожу в своих модульных тестах iPhone Objective-C, что я хочу отключить метод класса, напримерnsurlconnection's +sendSynchronousRequest: возвращает ответ: ошибка:способ.

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

- (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, который можно было бы написать для выполнения подобных задач?

Это было полезно?

Решение

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

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

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

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

Другие советы

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

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

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

Обновить

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

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

Видишь Особенности OCMock.

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

Пример кода, следующий за ответом Барри Уорка.

Поддельный класс, просто заглушающий 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

Переключение на макет и обратно:

{
    ...
    // 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);
}

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top