Question

Je trouve souvent dans les tests unitaires Objective-C de mon iPhone que je veux utiliser une méthode de classe, par exemple. NSUrlConnection's + sendSynchronousRequest: returnResponse: error: method.

Exemple simplifié:

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

Lorsque je lance ceci, je reçois:

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 difficulté à trouver de la documentation à ce sujet, mais je suppose que les méthodes de classe ne sont pas prises en charge par OCMock.

J'ai trouvé cette astuce après beaucoup de recherches sur Google. Cela fonctionne, mais est très lourd: http://thom.org.uk/ 2009/05/09 / mocking-class-method-in-objective-c /

Existe-t-il un moyen de le faire dans OCMock? Ou bien, est-ce que quelqu'un peut penser à un objet de catégorie OCMock intelligent qui pourrait être écrit pour réaliser ce genre de chose?

Était-ce utile?

La solution

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 (le fuseau horaire?: -).

Quoi qu'il en soit, je crois que cela n’est pas pris en charge comme on le souhaiterait dans OCMock, car il est impératif de remplacer une méthode de classe par une méthode littéralement dans la classe et de modifier son implémentation sans tenir compte du moment, du lieu ou du lieu. ou qui appelle la méthode. Cela contraste avec ce que semble faire OCMock, qui consiste à vous fournir un objet proxy que vous manipulez et que vous manipulez autrement directement et au lieu d'un objet "réel". objet de la classe spécifiée.

Par exemple, il semble raisonnable de vouloir remplacer NSURLConnection + sendSynchronousRequest: returnResponse: error: method. Cependant, il est typique que l’utilisation de cet appel dans notre code soit quelque peu enterrée, ce qui rend très difficile de le paramétrer et d’échanger un objet fictif pour la classe NSURLConnection.

Pour cette raison, je pense que la "méthode swizzling" L’approche que vous avez découverte, même si elle n’est pas sexy, correspond exactement à ce que vous voulez faire pour les méthodes de classe de substitution. Dire que c'est très très lourd semble extrême - que dirions-nous que c'est "inélégant"? et peut-être pas aussi pratique que OCMock rend la vie pour nous. Néanmoins, c’est une solution assez concise au problème.

Autres conseils

Mise à jour pour OCMock 3

OCMock a modernisé sa syntaxe pour prendre en charge le stubbing de méthode de classe:

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

Mettre à jour

OCMock prend désormais en charge le remplacement de méthode par une classe. Le code du PO devrait maintenant fonctionner tel que posté. S'il existe une méthode d'instance portant le même nom que la méthode de classe, la syntaxe est la suivante:

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

Voir Fonctionnalités d'OCMock .

Réponse originale

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

La fausse classe, stubbing juste 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

Passage vers et à partir de la maquette:

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

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

Si vous modifiez votre méthode sous test pour prendre un paramètre qui injecte la classe de NSURLConnection , il est relativement facile de passer un modèle 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 qui se moque de cette classe). Sans cette injection, vous utilisez une méthode de classe, qui utilise essentiellement NSURLConnection (la classe) en tant que singleton. Vous êtes donc tombé dans l'anti-modèle de l'utilisation d'objets singleton et la testabilité de votre code en a souffert. .

Le lien vers l'article de blog dans la question et l'argument RefuX m'ont inspiré pour proposer une mise en œuvre de leurs idées activée par le bloc: https : //gist.github.com/1038034

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top