Domanda

Trovo spesso nel mio iPhone Objective-C unit test che voglio stub out un metodo di classe, ad es. NSUrlConnection's + sendSynchronousRequest: returnResponse: error: method.

Esempio semplificato:

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

Quando eseguo questo, ottengo:

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

Ho avuto difficoltà a trovare documentazione su questo, ma presumo che i metodi di classe non siano supportati da OCMock.

Ho trovato questo suggerimento dopo un sacco di ricerche su Google. Funziona, ma è molto ingombrante: http://thom.org.uk/ 2009/05/09 / beffardi-class-metodi-in-Objective-C /

Esiste un modo per farlo all'interno di OCMock? Oppure qualcuno può pensare a un oggetto categoria OCMock intelligente che potrebbe essere scritto per realizzare questo genere di cose?

È stato utile?

Soluzione

Provenendo dal mondo di Ruby, capisco esattamente cosa stai cercando di realizzare. Apparentemente, eri letteralmente tre ore avanti di me cercando di fare esattamente la stessa cosa oggi (cosa del fuso orario? :-).

In ogni caso, credo che questo non sia supportato nel modo in cui si vorrebbe OCMock perché la stubbing di un metodo di classe deve letteralmente raggiungere la classe e cambiare la sua implementazione del metodo indipendentemente da quando, dove, o chi chiama il metodo. Ciò è in contrasto con ciò che OCMock sembra fare, ovvero fornirti un oggetto proxy che manipoli e su cui altrimenti operi direttamente e al posto di un "reale" oggetto della classe specificata.

Ad esempio, sembra ragionevole voler stub NSURLConnection + sendSynchronousRequest: returnResponse: error: method. Tuttavia, è tipico che l'uso di questa chiamata all'interno del nostro codice sia in qualche modo sepolto, rendendo così imbarazzante parametrizzarlo e scambiarlo in un oggetto simulato per la classe NSURLConnection.

Per questo motivo, penso che il metodo "quot swizzling" l'approccio che hai scoperto, anche se non sexy, è esattamente quello che vuoi fare per superare i metodi di classe. Dire che è molto ingombrante sembra estremo - che ne dici di essere d'accordo che è "inelegante" e forse non è conveniente come OCMock crea la vita per noi. Tuttavia, è una soluzione abbastanza concisa al problema.

Altri suggerimenti

Aggiornamento per OCMock 3

OCMock ha modernizzato la sua sintassi per supportare lo stub del metodo di classe:

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

Aggiorna

OCMock ora supporta il metodo di classe stub out of the box. Il codice OP ora dovrebbe funzionare come pubblicato. Se esiste un metodo di istanza con lo stesso nome del metodo class, la sintassi è:

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

Vedi Funzioni di OCMock .

Risposta originale

Codice di esempio che segue la risposta di Barry Wark.

La classe falsa, solo stubing 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

Passaggio da e verso la simulazione:

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

Ecco una bella "idea" con un'implementazione rapida dei metodi di classe: https://gist.github.com / 314009

Se modifichi il tuo metodo sotto test per prendere un parametro che inietta la classe del NSURLConnection , allora è relativamente facile passare un mock che risponde al selettore dato (potresti dover creare una classe fittizia nel modulo di test che ha il selettore come metodo di istanza e deride quella classe). Senza questa iniezione, stai usando un metodo di classe, essenzialmente usando NSURLConnection (la classe) come singleton e quindi sono caduti nel modello anti-uso di oggetti singleton e la testabilità del tuo codice ha subito .

Link al post sul blog nella domanda e il refuX gist mi ha ispirato a presentare l'implementazione delle loro idee abilitata per blocco: https : //gist.github.com/1038034

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top