36 votes

Comment stub une méthode de classe dans OCMock ?

Je trouve souvent dans mes tests unitaires Objective-C pour iPhone que je veux stub out une méthode de classe, par exemple la méthode +sendSynchronousRequest:returningResponse:error : de NSUrlConnection.

Exemple simplifié :

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

Quand je l'exécute, j'obtiens :

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

J'ai eu beaucoup de mal à trouver de la documentation à ce sujet, mais je suppose que les méthodes de classe ne sont pas supportées par OCMock.

J'ai trouvé cette astuce après avoir beaucoup cherché sur Google. Elle fonctionne, mais est très encombrante : http://thom.org.uk/2009/05/09/mocking-class-methods-in-objective-c/

Y a-t-il un moyen de faire cela dans OCMock ? Ou quelqu'un peut-il penser à une catégorie d'objet OCMock intelligente qui pourrait être écrite pour accomplir ce genre de chose ?

46voto

Ben Flynn Points 5346

Mise à jour pour OCMock 3

OCMock a modernisé sa syntaxe pour supporter le stubbing des méthodes de classe :

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

Mise à jour

OCMock prend désormais en charge le stubbing des méthodes de classe. Le code de l'OP devrait maintenant fonctionner comme indiqué. S'il y a une méthode d'instance avec le même nom que la méthode de classe, la syntaxe est :

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

Ver Caractéristiques de OCMock .

Réponse originale

Exemple de code suivant la réponse de Barry Wark.

La fausse classe, qui n'est qu'un raccourci de 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

Commutation vers et depuis le simulateur :

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

17voto

mharper Points 1755

Venant du monde de Ruby, je comprends exactement ce que vous essayez d'accomplir. Apparemment, vous aviez littéralement trois heures d'avance sur moi pour essayer de faire exactement la même chose aujourd'hui (problème de fuseau horaire ? :-).

Bref, je croire que cela n'est pas supporté de la manière dont on le souhaiterait dans OCMock parce que le stubbing d'une méthode de classe doit littéralement atteindre la classe et changer l'implémentation de sa méthode sans tenir compte du moment, de l'endroit ou de la personne qui appelle la méthode. Ceci est en contraste avec ce que OCMock semble faire qui est de vous fournir un objet proxy que vous manipulez et sur lequel vous opérez directement et à la place d'un "vrai" objet de la classe spécifiée.

Par exemple, il semble raisonnable de vouloir stuber la méthode NSURLConnection +sendSynchronousRequest:returningResponse:error :. Cependant, il est courant que l'utilisation de cet appel dans notre code soit quelque peu enfouie, ce qui rend très difficile de le paramétrer et d'échanger un objet fantaisie pour la classe NSURLConnection.

Pour cette raison, je pense que l'approche "method swizzling" que vous avez découverte, bien que peu sexy, est exactement ce que vous voulez faire pour stubber les méthodes des classes. Dire que c'est muy encombrant semble extrême -- que diriez-vous de convenir que c'est "inélégant" et peut-être pas aussi pratique que OCMock nous rend la vie. Néanmoins, c'est une solution assez concise au problème.

6voto

RefuX Points 328

Voici un bon "gist" avec une implémentation de swizzle pour les méthodes de classe : https://gist.github.com/314009

4voto

Barry Wark Points 73462

Si vous modifiez votre méthode testée pour qu'elle prenne un paramètre qui injecte la classe de l'utilisateur de la méthode. NSURLConnection Il est alors relativement facile de passer un objet fantaisie qui répond au sélecteur donné (vous devrez peut-être créer une classe factice dans votre module de test qui a le sélecteur comme méthode d'instance et faire un objet fantaisie de cette classe). Sans cette injection, vous utilisez une méthode de classe, en utilisant essentiellement NSURLConnection (la classe) comme un singleton et vous êtes donc tombé dans l'anti-modèle de l'utilisation d'objets singletons et la testabilité de votre code en a souffert.

2voto

Pavel Kunc Points 76

Le lien vers l'article de blog dans la question et le résumé de RefuX m'ont inspiré une mise en œuvre de leurs idées par blocs : https://gist.github.com/1038034

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X