2011-12-30 5 views
13

Ho una configurazione piuttosto semplice per questo test dell'unità. Ho una classe che ha una proprietà delegato:Perché la proprietà del delegato debole dell'oggetto mio è nullo nei test di unità?

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

e ho impostato il delegato nel mio test:

- (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."); 
} 

Ma il delegato non è in realtà costituito sulla linea 3 del mio test di unità, in modo # someMethod non viene mai chiamato. Quando lo cambio in

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

non ci riesce. Sto usando ARC, quindi la mia impressione era che la debole proprietà fosse stata scartata. Abbastanza sicuro, cambiandolo a strong passa il passaggio STAssertNotNil. Ma non voglio farlo con un delegato, e non capisco perché questo faccia la differenza qui. Da quello che ho letto, tutti i riferimenti locali in ARC sono i passaggi strong e STAssertNotNil(delegate). Perché la mia proprietà delegato debole è nullo quando lo stesso oggetto in una variabile locale non lo è?

risposta

8

Questo è un bug nel runtime di iOS. La seguente discussione ha più dettagli. In poche parole, il runtime di iOS ARC non sembra gestire i deboli riferimenti ai proxy. Il runtime di OSX può.

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

Per quanto ho capito dalla discussione un bug report è stata depositata presso Apple. Se qualcuno ha un'idea sensata per una soluzione alternativa ...

+0

+1 Ottimo, sembra di avere [l'idea giusta] (http://stackoverflow.com/a/9058542/31158) .... Grazie per aver trovato le informazioni giuste! –

+0

Potrebbe essere utile provare il simulatore di iOS 6.0. Se il problema debole/proxy è un bug di runtime (ed è), questo potrebbe essere risolto in iOS 6.0. L'ho provato, ma non posso commentarlo (dato che è ancora sotto NDA). Ma dovresti davvero provarlo. Veramente. – Jelle

1

Non sono un esperto ARC ma la mia ipotesi è che mockDelegateHelper() restituisca un oggetto debole. Di conseguenza, delegate è nullo prima dell'esecuzione della seconda riga di codice. Mi permetto di indovinare che lo mockDelegateHelper() è il colpevole o che OCMock sta intralciando il modo in cui manipola e crea oggetti.

+0

La rimozione della funzione dal flusso non ha avuto alcun effetto, ma la rimozione di OCMock ha risolto il problema. Ho creato una classe esplicita che è conforme al mio protocollo e l'ho usata invece, e il delegato non è più nulla. Questo è sfortunato per i miei test, ma almeno risponde alla mia domanda. Grazie. –

4

Non so davvero cosa sta succedendo qui, ma OCMock restituisce un autorappartamento NSProxy -descendente dal metodo mockForProtocol:, che ritengo sia giusto. Forse ARC ha problemi con NSProxies? Comunque, ho superare questo problema dichiarando la variabile __weak:

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

E 'davvero non ha bisogno di essere __strong (il default), in questo caso, come è autoreleased e non si sta tenendo in giro. ..

+0

wow, questo ha funzionato per me – rodowi

+0

qualche suggerimento o spiegazione che vorresti condividere su questo? – rodowi

+1

@rodbot: beh, ho solo pensato che ARC avesse un problema con 'NSProxy's perché questo è quello che usa OCMock, e dichiarando una variabile con' __weak' stai praticamente dicendo ad ARC di non dargli fastidio. –

2

Una soluzione alternativa consiste nell'utilizzare Mazzi parziali.

@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

Funziona per me come soluzione alternativa. –

+0

Ma non è perfetto. Richiede di includere dipendenze da altre classi rispetto alla classe che si sta testando –

+1

Non devo includere anche le dipendenze se lo faccio in modo normale e indolore '[OCMockObject mockForProtocol: @protocol (MyDelegateProtocol)]'? – fabb