2009-09-09 5 views
4

J'ai une interface avec une méthode CopyFrom() qui copie toutes les propriétés d'un autre objet. J'ai un test qui exécute plusieurs VerifyGet() appelle à faire en sorte que chaque propriété a été extraite de l'objet passé, par exemple:En utilisant Moq, comment vérifier toutes les propriétés d'un objet ont été copiées?

Thing target = new Thing(); 
IThing source = new Mock<IThing>(); 

target.CopyFrom(source.Object); 
source.VerifyGet(t => t.Foo); 
source.VerifyGet(t => t.Bar); 

Je voudrais un moyen d'itérer sur les propriétés de IThing bien et vérifier que chaque a été copié automatiquement afin que le test échoue si quelqu'un ajoute une propriété mais oublie de le copier. Y a-t-il un moyen de le faire via Moq? J'ai essayé;

foreach (var prop in typeof(IThing).GetProperties()) 
{ 
    source.VerifyGet(t => prop.Invoke(t, null)); 
} 

mais cela n'a pas fonctionné puisque le lambda ne représentait pas un accesseur de propriété. Je pense qu'il devrait y avoir un moyen de créer quelque chose via la classe Expression, mais je ne suis pas assez familier avec LINQ pour comprendre ce qui devrait être là.

+0

Cela ne répond pas à votre question, mais vous devriez vraiment envisager d'utiliser AutoMapper pour ce genre de choses. Cela évitera les mappages de propriétés oubliés, surtout si leurs noms sont identiques. –

Répondre

4

Je ne pense pas que ce soit possible avec Moq, mais vous devez d'abord vous demander si votre test est pertinent. Que voulez-vous vraiment tester ici?

Est-il important que la méthode CopyFrom lit des propriétés? Il pourrait certainement lire toutes les propriétés sans leur écrire à la nouvelle instance, donc un tel test basé sur l'interaction ne prouve vraiment rien.

Je suppose que ce que vous voulez vraiment tester, c'est que les propriétés de la cible sont égales aux propriétés de la source?

En supposant que les propriétés sur IThing sont inscriptible, vous pouvez créer un stub avec toutes les propriétés définies à l'aide de la méthode SetupAllProperties:

var sourceStub = new Mock<IThing>(); 
sourceStub.SetupAllProperties(); 
sourceStub.Object.Bar = "Bar"; 
sourceStub.Object.Foo = "Foo"; 

Vous devrez alors comparer la cible à la source pour voir si tous les propriétés correspondent. Vous pouvez le faire en implémentant une méthode Test-Specific Equals dans une classe qui enveloppe la cible réelle.

Si vous pensez que c'est trop de travail, vous voudrez peut-être vérifier la classe de ressemblance de AutoFixture qui vous donne une comparaison générale d'égalité spécifique au test. Cela vous permettra de continuer le test comme celui-ci:

var expectedResult = new Likeness<IThing>(sourceStub.Object); 

target.CopyFrom(sourceStub.Object); 

Assert.AreEqual(expectedResult, target); 

Ressemblance utilise la réflexion pour faire une boucle sur toutes les propriétés publiques dans l'objet enveloppé et voir si l'objet par rapport a les mêmes valeurs pour ces propriétés.

+0

Vous avez raison de dire que VerifyGet() ne signifie pas nécessairement que tout a été copié, mais il était assez proche pour mon cas. Le problème avec le simple parcours et la vérification de toutes les valeurs est le même: si quelqu'un ajoute une propriété et oublie de la copier, alors dans le test, les deux propriétés seront simplement la valeur par défaut et le test passera tout de même. –

+0

@OAB: C'est vrai, à moins que vous n'utilisiez un évaluateur d'égalité basé sur Reflection comme Likeness (jeu de mots). –

+0

Comment Likeness évite-t-il le problème de la propriété ayant la valeur par défaut?Si j'ajoute une nouvelle propriété de chaîne Baz à IThing et que j'oublie de l'ajouter à CopyFrom, ne trouvera-t-on pas que Baz sur les deux sourcesStub et target est nul? –

1
/// <summary> 
/// Verifies that a property was read on the mock 
/// </summary> 
public static void VerifyGet<T>(this Mock<T> mockedObject, string propertyName) where T : class 
{ 
    var property = typeof(T).GetProperty(propertyName); 
    if (property == null) 
     throw new ArgumentException(string.Format("No property by the name '{0}' was found on '{1}'.", propertyName, typeof(T).Name)); 

    // getPropFuncExpression = obj => obj.propertyName;   
    var parameterExpression = Expression.Parameter(typeof(T), typeof(T).Name); 
    var propertyExpression = Expression.Property(parameterExpression, property); 
    var getPropFuncExpression = Expression.Lambda(propertyExpression, parameterExpression); 

    var verifyGet = mockedObject.GetType().GetMethods().Single(m => m.Name == "VerifyGet" && m.GetParameters().Length == 1); 
    verifyGet.MakeGenericMethod(property.PropertyType).Invoke(mockedObject, new object[] { getPropFuncExpression }); 
} 

Vous pouvez ajouter la méthode d'extension ci-dessus afin que vous puissiez simplement appeler:

foreach (var prop in typeof(IThing).GetProperties()) 
{ 
    source.VerifyGet(prop.Name); 
} 
Questions connexes