iphone objective-c cocoa-touch ocmock

iphone - ¿Cómo encontrar un método de clase en OCMock?



objective-c cocoa-touch (5)

Aquí hay un buen ''truco'' con una implementación Swizzle para métodos de clase: https://gist.github.com/314009

A menudo encuentro en las pruebas de mi unidad iPhone Objective-C que quiero anular un método de clase, por ejemplo NSUrlConnection''s + sendSynchronousRequest: returningResponse: error: method.

Ejemplo simplificado:

- (void)testClassMock { id mock = [OCMockObject mockForClass:[NSURLConnection class]]; [[[mock stub] andReturn:nil] sendSynchronousRequest:nil returningResponse:nil error:nil]; }

Cuando ejecuto esto, obtengo:

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

Me ha costado mucho encontrar documentación sobre esto, pero supongo que OCMock no admite los métodos de clase.

Encontré este consejo después de un montón de Google. Funciona, pero es muy engorroso: http://thom.org.uk/2009/05/09/mocking-class-methods-in-objective-c/

¿Hay alguna forma de hacerlo en OCMock? ¿O alguien puede pensar en un objeto de categoría OCMock inteligente que podría escribirse para lograr este tipo de cosas?


Enlace al blogpost en la pregunta y RefuX gist me inspiró para llegar a la implementación habilitada de bloques de sus ideas: gist.github.com/1038034


Si modifica su método bajo prueba para tomar un parámetro que inyecte la clase de NSURLConnection , entonces es relativamente fácil pasar un simulacro que responda al selector dado (puede que tenga que crear una clase ficticia en su módulo de prueba que tenga el selector como un método de instancia y simulacro de esa clase). Sin esta inyección, estás usando un método de clase, esencialmente usando NSURLConnection (la clase) como singleton y, por lo tanto, has caído en el patrón de antipico de usar objetos singleton y la capacidad de prueba de tu código ha sufrido.


Viniendo del mundo de Ruby, entiendo exactamente lo que estás tratando de lograr. Aparentemente, estuviste literalmente tres horas adelante de mí tratando de hacer exactamente lo mismo hoy (¿cosa de la zona horaria? :-).

De todos modos, creo que esto no se admite de la manera que uno desearía en OCMock porque anotar un método de clase necesita literalmente llegar a la clase y cambiar su implementación de método independientemente de cuándo, dónde o quién llame al método. Esto está en contraste con lo que parece hacer OCMock, que es proporcionarle un objeto proxy que manipule y opere directamente y en lugar de un objeto "real" de la clase especificada.

Por ejemplo, parece razonable querer insertar NSURLConnection + sendSynchronousRequest: returningResponse: error: method. Sin embargo, es típico que el uso de esta llamada dentro de nuestro código esté algo oculto, por lo que es muy difícil parametrizarlo e intercambiar un objeto simulado para la clase NSURLConnection.

Por esta razón, creo que el enfoque "método swizzling" que has descubierto, aunque no es sexy, es exactamente lo que quieres hacer para anotar los métodos de clase. Decir que es muy engorroso parece extremo: ¿qué tal si coincidimos en que es "poco elegante" y tal vez no tan conveniente como OCMock nos hace la vida? Sin embargo, es una solución bastante concisa para el problema.


Actualización para OCMock 3

OCMock ha modernizado su sintaxis para admitir el trozo de método de clase:

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

Actualizar

OCMock ahora es compatible con el método de clasificación de clase fuera de la caja. El código del OP ahora debería funcionar como publicado. Si hay un método de instancia con el mismo nombre que el método de clase, la sintaxis es:

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

Ver las características de OCMock .

Respuesta original

Código de muestra después de la respuesta de Barry Wark.

La clase falsa, solo conexión de conexiónWithRequest: delegar:

@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

Cambiando hacia y desde el simulacro:

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