2009-11-27 16 views
36

A menudo encuentro en mi iPhone pruebas de unidad Objective-C que quiero anular un método de clase, p. NSUrlConnection's + sendSynchronousRequest: returningResponse: error: method.¿Cómo encontrar un método de clase en OCMock?

ejemplo simplificado:

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

Cuando se ejecuta esto, me sale:

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

que he tenido un momento muy difícil encontrar ninguna documentación sobre esto, pero supongo que los métodos de la clase Aren' Compatible con OCMock.

Encontré este consejo después de mucha búsqueda en Google. Funciona, pero es muy engorroso: http://thom.org.uk/2009/05/09/mocking-class-methods-in-objective-c/

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

+1

Nueva URL de la entrada en el blog: http://thomlawrence.wordpress.com/ 2009/05/09/mocking-class-methods-in-objective-c/ – RefuX

+3

El enlace original de la publicación de blog es bueno, el enlace de wordpress.com está muerto. –

+4

Comenzando con la versión 2.1 de OCMock esto se admite de fábrica. Ahora puede anclar los métodos de clase de la misma forma que los métodos de instancia de stub. –

Respuesta

15

Viniendo desde el 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 en el camino uno desearía en OCMock porque stubbing un método de clase tiene que llegar, literalmente, en la clase y cambia su implementación del método, independientemente de cuándo, dónde o quien llama a la 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 ha descubierto, aunque no es sexy, es exactamente lo que quiere hacer para los métodos de clase de stubbing. Decir que es muy engorroso parece extremo: qué tal si estamos de acuerdo 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.

+0

Bastante, aceptaré "poco elegante" o "engorroso" sin el mismo. ;). Aunque sé que llega a la clase y cambia la implementación del método, todavía me gustaría poder hacerlo a través de OCMock siempre que pueda deshacerlo al final de la prueba. En una nota relacionada, ¿es posible hacer ese tipo de cambio generalizado en los métodos de instancias mediante OCMock? es decir, ¿cambiar todos los objetos del tipo NSString para tener una implementación diferente de -lowercaseString? Sé que puedes hacer esto con el método swizzling, pero a veces también lo encuentro útil cuando uso OCMock. – Jeremy

+17

Si vienes aquí a través de una búsqueda, esto ahora se admite de fábrica en OCMock. Ahora puede anclar los métodos de clase de la misma forma que los métodos de instancia de stub. –

4

Si modifica su método bajo prueba para tomar un parámetro que inyecte la clase del NSURLConnection, entonces es relativamente fácil pasar un simulacro que responda al selector dado (puede que tenga que crear una clase ficticia en su prueba módulo que tiene el selector como un método de instancia y se burla 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 antipatrón de usar objetos singleton y la capacidad de prueba de tu código ha sufrido.

6

Aquí es un buen 'quid' con una aplicación swizzle de métodos de clase: https://gist.github.com/314009

+0

¡Esto es una idea excelente! ¡Gracias! –

+2

Me topé con un método de clase mejorado 'swizzler': https://gist.github.com/1038034 – penfold

+0

Nota reborgs comentario sobre la esencia y el método de cambio mockMethod = class_getInstanceMethod (aNewClass, aNewMethod); en el Método mockMethod = class_getClassMethod (aNewClass, aNewMethod); – cschuff

2

Enlace a la entrada de blog en la pregunta y síntesis RefuX me inspiró para llegar a permitido bloquear la aplicación de sus ideas: https://gist.github.com/1038034

40

actualización para OCMock 3

OCMock ha modernizado su sintaxis para apoyar método de clase stubbing:

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

actualización

OCMock ahora soporta método de clase que tropieza hacia 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 OCMock's Features.

respuesta original

El código de ejemplo siguiente respuesta de Barry Wark.

La clase falso, simplemente apagando connectionWithRequest: delegado:

@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 

conmutación desde y hacia la maqueta:

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

¡gracias por la actualización de OCMock que respalda la eliminación de clases de la caja! Quiero comprobar que se llama a un método de clase con ciertos parámetros (usando 'expect'). ¿Sabes si esto también es posible? – nacho4d

+0

@ nacho4d Definitivamente. Expect, reject, stub: todos funcionan como lo harían con los métodos de instancia. –

Cuestiones relacionadas