2010-08-21 3 views
24

Nous utilisons Moq pour tester nos classes de service, mais nous sommes bloqués sur la façon de tester les situations où une méthode de service appelle une autre méthode de service de la même classe. J'ai essayé de définir la méthode appelée virtuelle, mais je n'arrivais toujours pas à savoir quoi faire dans Moq. Par exemple:Utilisation de Moq pour remplacer les méthodes virtuelles dans la même classe

public class RenewalService : IRenewalService 
{ 
    //we've already tested this 
    public virtual DateTime? GetNextRenewalDate(Guid clientId) 
    { 
     DateTime? nextRenewalDate = null; 
     //...<snip> a ton of already tested stuff... 

     return nextRenewalDate; 
    } 

    //but want to test this without needing to mock all 
    //the methods called in the GetNextRenewalDate method 
    public bool IsLastRenewalOfYear(Renewal renewal) 
    { 
     DateTime? nextRenewalDate = GetNextRenewalDate(renewal.Client.Id); 
     if (nextRenewalDate == null) 
      throw new Exceptions.DataIntegrityException("No scheduled renewal date, cannot determine if last renewal of year"); 
     if (nextRenewalDate.Value.Year != renewal.RenewDate.Year) 
      return true; 
     return false; 
    } 
} 

Dans l'exemple ci-dessus, notre méthode de GetNextRenewalDate est assez compliqué, et nous avons déjà testé l'unité. Cependant, nous voulons tester le plus simple IsLastRenewalOfYear sans avoir besoin de se moquer de tout ce qui est nécessaire pour GetNextRenewalDate. Fondamentalement, nous voulons juste se moquer de GetNextRenewalDate.

Je me rends compte que je pourrais créer une nouvelle classe qui remplace GetNextRenewalDate et tester la nouvelle classe, mais est-il possible de tirer parti de Moq pour rendre cela plus simple?

Répondre

41

Vous pouvez probablement utiliser un mock partiel dans ce scénario, bien que toutes vos méthodes auraient besoin d'être virtuel:

var mock = new Moq.Mock<RenewalService>(); 
    mock.Setup(m => m.GetNextRenewalDate(It.IsAny<Guid>())).Returns(null); 
    mock.CallBase = true; 
    var results = mock.Object.IsLastRenewalOfYear(...); 
+0

Cela semble fonctionner ... Je n'avais même pas besoin de tout rendre virtuel (je l'ai juste gardé le même que ci-dessus). – Andrew

+3

@Andrew. Ce qui précède fonctionne parce que 'Mock.CallBase == true' signifie que les invocations qui ne correspondent pas à une configuration appellent l'implémentation sous-jacente. Ainsi, 'IsLastRenewalOfYear' appellera l'implémentation, parce que ce n'est pas virtuel, mais' GetNextRenewalDate' renverra 'null' car le programme d'installation sera toujours reconnu. Le code suivant fonctionnera même si 'IsLastRenewalOfYear' ** est ** virtuel. –

+1

nous avons utilisé cela dans quelques autres endroits aussi, travaillé comme un charme. Merci! – Andrew

0

Modifier: Je suis maintenant d'accord que ce n'est pas la bonne réponse pour le cas d'Andrew. Je veux laisser cette réponse ici pour le fil des commentaires. S'il vous plaît ne pas voter vers le bas plus :)

Avant modifier:

En général, les cadres simulacres d'objet ne sont pas conçus pour simplifier le scénario de classe unique, ils sont conçus pour isoler votre code afin que vous puissiez tester une seule classe.

Si vous essayez d'utiliser un cadre d'objet fictif pour résoudre ce problème, le framework va simplement créer une classe dérivée et surcharger cette méthode. La seule différence sera que vous pouvez le faire en 3 lignes au lieu de 5, car vous n'aurez pas à créer la définition de la classe dérivée.

Si vous souhaitez utiliser des objets fantaisie pour isoler ce comportement, vous devez fractionner cette classe un peu. La logique GetNextRenewalDate peut vivre à l'extérieur de l'objet RenewalService.

Le fait que vous rencontriez ce problème peut montrer qu'il existe une conception plus simple ou plus fine à découvrir. Trouver un cours un peu moins concret, avec un nom comme «manager» ou «service», est souvent un indice que vous pourriez décomposer votre conception en classes plus petites et obtenir une meilleure réutilisabilité et maintenabilité.

+0

Je ne sais pas si la rupture de notre conception ferait plus maintenable .. Si nous passions de 5 000 classes (environ ce que nous avons actuellement) à 10 000 classes, il ne serait guère plus facile de naviguer. – Andrew

+0

briser le design simplement pour le rendre plus testable dans un ensemble d'outils spécifique semble être une mauvaise idée. En descendant ce chemin, il est rare que les méthodes appellent d'autres méthodes publiques dans une classe. Comment est ce bon design? –

+0

@Andrew, Jess: Je ne connais pas son application ou ses exigences, alors n'hésitez pas à prendre ou à laisser mes conseils. Oui, le briser juste pour avoir des classes à une méthode est stupide, mais le reste de mon conseil est toujours valable. Souvent, quand j'ai vu un cours de "manager" ou de "service", les abstractions ne sont pas au bon endroit. Si un tel refactor devait être fait, les méthodes d'autres classes pourraient finir ici, ou ces méthodes pourraient se retrouver dans d'autres classes, faisant disparaître complètement cette classe. –

0
var mock = new Moq.Mock<RenewalService> { CallBase = true }; 
mock.Setup(m => m.GetNextRenewalDate(It.IsAny<Guid>())).Returns(null); 
var results = mock.Object.IsLastRenewalOfYear(...); 
Questions connexes