2011-12-30 13 views
13

Tengo una configuración bastante simple para esta prueba unitaria. Tengo una clase que tiene una propiedad delegado:¿Por qué la propiedad delegada débil de mi objeto es nula en las pruebas de mi unidad?

@interface MyClass : NSObject 
... 
@property (nonatomic, weak) id<MyDelegateProtocol> connectionDelegate; 
... 
@end 

y me puse el delegado en mi prueba:

- (void)testMyMethod_WithDelegate { 
    id delegate = mockDelegateHelper(); // uses OCMock to create a mock object 
    [[delegate expect] someMethod]; 
    myClassIvar.connectionDelegate = delegate; 
    [myClass someOtherMethod]; 
    STAssertNoThrow([delegate verify], @"should have called someMethod on delegate."); 
} 

Sin embargo, el delegado no el valor definido en la línea 3 de mi prueba de la unidad, por lo que # someMethod nunca se llama. Cuando cambio a

myClassIvar.connectionDelegate = delegate; 
STAssertNotNil(myClassIvar.connectionDelegate, @"delegate should not be nil"); 

que no existe. Estoy usando ARC, así que mi corazonada fue que la propiedad débil estaba siendo desasignada. Efectivamente, cambiarlo a strong hace que pase el STAssertNotNil. Pero no quiero hacer eso con un delegado, y no entiendo por qué eso hace la diferencia aquí. Por lo que he leído, todas las referencias locales en ARC son strong, y STAssertNotNil(delegate) pasa. ¿Por qué mi propiedad delegación débil es nula cuando el mismo objeto en una variable local no?

Respuesta

8

Esto es un error en el tiempo de ejecución de iOS. La siguiente discusión tiene más detalles. En pocas palabras, el tiempo de ejecución de iOS ARC parece no poder manejar las referencias débiles a los proxies. El tiempo de ejecución de OSX puede.

http://www.mulle-kybernetik.com/forum/viewtopic.php?f=4&t=252

Por lo que yo entiendo de la discusión un informe de error se ha presentado con Apple. Si alguien tiene una idea sensata para una solución alternativa ...

+0

+1 Genial, parece que tuve [la idea correcta] (http://stackoverflow.com/a/9058542/31158) ... ¡Gracias por encontrar la información correcta! –

+0

Es posible que desee probar el simulador iOS 6.0. Si el problema débil/Proxy es un error de tiempo de ejecución (y lo es), esto podría resolverse en iOS 6.0. Lo probé, pero no puedo comentar sobre él (ya que todavía está bajo NDA). Pero realmente deberías intentarlo. De Verdad. – Jelle

1

No soy experto en ARC pero supongo que mockDelegateHelper() devuelve un objeto débil. Como resultado, delegate es nulo antes de que se ejecute la segunda línea de código. Me atrevería a adivinar que el mockDelegateHelper() es el culpable o que OCMock está obstaculizando la forma en que manipula y crea objetos.

+0

Eliminar la función del flujo no lo afectó, pero eliminar OCMock solucionó el problema. Hice una clase explícita que se ajusta a mi protocolo y la utilicé en su lugar, y el delegado ya no es nulo. Esto es desafortunado para mi prueba, pero al menos responde mi pregunta. Gracias. –

4

Realmente no sé lo que está sucediendo aquí, pero OCMock devuelve un NSProxy autodescripto -descendiente del método mockForProtocol:, que creo que es correcto. Quizás ARC tiene problemas con NSProxies? De todos modos, he superado este problema al declarar la variable __weak:

- (void)testMyMethod_WithDelegate { 
    // maybe you'll also need this modifier inside the helper 
    __weak id delegate = mockDelegateHelper(); 
    ... 

Realmente no tiene por qué ser __strong (por defecto), en este caso, ya que es autoreleased y no está manteniendo a su alrededor. ..

+0

wow, esto solo funcionó para mí – rodowi

+0

¿Alguna pista o explicación que quisiera compartir al respecto? – rodowi

+1

@rodbot: bueno, solo pensé que ARC tenía un problema con 'NSProxy's porque eso es lo que utiliza OCMock, y al declarar una variable con' __weak' básicamente le está diciendo a ARC que no se moleste con eso. –

2

Una solución consiste en utilizar Parcial Mocks.

@interface TestMyDelegateProtocolDelegate : NSObject <MyDelegateProtocol> 
@end 

@implementation TestMyDelegateProtocolDelegate 
- (void)someMethod {} 
@end 


@implementation SomeTest { 
- (void)testMyMethod_WithDelegate { 
    id<MyDelegateProtocol> delegate = [[TestMyDelegateProtocolDelegate] alloc] init]; 
    id delegateMock = [OCMockObject partialMockForObject:delegate] 
    [[[delegateMock expect] someMethod] 
    myClassIvar.connectionDelegate = delegate; 
    [myClass someOtherMethod]; 
    STAssertNoThrow([delegate verify], @"should have called someMethod on delegate."); 
} 
@end 
+0

Funciona para mí como solución alternativa. –

+0

Pero no es perfecto. Requiere incluir dependencias de otras Clases además de la Clase que está probando –

+1

. No tengo que incluir dependencias si lo hago de la manera normal sin problemas '[OCMockObject mockForProtocol: @protocol (MyDelegateProtocol)]'? – fabb

Cuestiones relacionadas