Comment combiner les assertions de collection et de propriété à l'aide d'assertions fluides ?
-
14-11-2019 - |
Question
Je voudrais "combiner" les assertions de collection et les assertions de propriété de Fluent Assertion, par ex.affirmer que deux IEnumerable
sont égaux par paire en utilisant une comparaison propriété par propriété (éventuellement "imbriquée") (c'est-à-direégalité structurelle, dans le langage fonctionnel).
Exemple concret :
var dic = new Dictionary<int, string>() { {1, "hi"}, {2, "bye" } };
var actual = dic.ToSelectListItems(0).OrderBy(si => si.Text);
var expected = new List<SelectListItem>() {
new SelectListItem() {Selected = false, Text="bye", Value="2"},
new SelectListItem() {Selected = false, Text="hi", Value="1"}
};
Ici, j'ai écrit une méthode d'extension ToSelectListItems
qui convertit un Dictionary
à un IEnumerable
de SelectListItem
s (à partir d'ASP.NET MVC).Je veux affirmer que actual
et expected
sont "structurellement" égaux, notant que le type de référence SelectListItem
ne remplace pas Equal
s et utilise donc l'égalité de référence par défaut.
Mise à jour
J'utilise actuellement la solution suivante, élaborée à la main, en espérant toujours quelque chose de mieux intégré à FluentAssertions :
public static void ShouldBeStructurallyEqualTo<T, U>(this IEnumerable<T> actual, IEnumerable<U> expected) {
actual.Should().HaveCount(expected.Count());
actual.Zip(expected).ForEach(pair => pair.Item1.ShouldHave().AllProperties().IncludingNestedObjects().EqualTo(pair.Item2));
}
(note: Zip
voici le mien IEnumerable
extension qui utilise Tuple.Create
comme projection par défaut)
Mise à jour 2
Voici deux exemples minimaux :
public class FooBar {
public string Foo { get; set; }
public int Bar { get; set; }
}
public class TestClass {
[Test]
public void MinimalExample() {
List<FooBar> enumerable1 = new List<FooBar>() { new FooBar() { Foo = "x", Bar = 1 }, new FooBar() { Foo = "y", Bar = 2 } };
List<FooBar> enumerable2 = new List<FooBar>() { new FooBar() { Foo = "x", Bar = 1 }, new FooBar() { Foo = "y", Bar = 2 } };
enumerable1.ShouldHave().SharedProperties().IncludingNestedObjects().EqualTo(enumerable2);
//Test 'TestClass.MinimalExample' failed: System.Reflection.TargetParameterCountException : Parameter count mismatch.
// at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
// at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
// at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
// at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
// at FluentAssertions.Assertions.PropertyEqualityValidator.AssertSelectedPropertiesAreEqual(Object subject, Object expected)
// at FluentAssertions.Assertions.PropertyEqualityValidator.Validate(UniqueObjectTracker tracker, String parentPropertyName)
// at FluentAssertions.Assertions.PropertyEqualityValidator.Validate()
// at FluentAssertions.Assertions.PropertyAssertions`1.EqualTo(Object otherObject, String reason, Object[] reasonArgs)
// at FluentAssertions.Assertions.PropertyAssertions`1.EqualTo(Object otherObject)
// MiscAssertions.cs(32,0): at TestClass.MinimalExample()
}
[Test]
public void MinimalExample2() {
IEnumerable<FooBar> enumerable1 = (new List<FooBar>() { new FooBar() { Foo = "x", Bar = 1 }, new FooBar() { Foo = "y", Bar = 2 } }).Cast<FooBar>();
FooBar[] enumerable2 = new [] { new FooBar() { Foo = "x", Bar = 1 }, new FooBar() { Foo = "y", Bar = 2 } };
enumerable1.ShouldHave().SharedProperties().IncludingNestedObjects().EqualTo(enumerable2);
//Test 'TestClass.MinimalExample2' failed: System.InvalidOperationException : Please specify some properties to include in the comparison.
// at FluentAssertions.Assertions.PropertyEqualityValidator.Validate(UniqueObjectTracker tracker, String parentPropertyName)
// at FluentAssertions.Assertions.PropertyEqualityValidator.Validate()
// at FluentAssertions.Assertions.PropertyAssertions`1.EqualTo(Object otherObject, String reason, Object[] reasonArgs)
// at FluentAssertions.Assertions.PropertyAssertions`1.EqualTo(Object otherObject)
// MiscAssertions.cs(52,0): at TestClass.MinimalExample2()
}
}
La solution
J'ai ajouté la prise en charge de votre scénario dans la branche principale de Fluent Assertions.Il fera partie de la prochaine version, mais cela pourrait nous prendre un mois ou deux pour accumuler suffisamment de modifications pour justifier une autre version.Si vous le souhaitez, vous pouvez récupérer la version source et exécuter release.bat pour créer une version intermédiaire.
Autres conseils
Si j'interprète correctement votre question, je pense que vous devriez essayer la version 1.7.0 d'affirmations fluides.Dans cette version, nous avons modifié le comportement : lorsque l'outil IncludeNestedObjects est utilisé, il le fera également sur les collections d'objets.Un extrait de la documentation.
"De plus, vous pouvez pousser la comparaison structurelle un niveau plus loin en incluant la propriété IncludeNestedObjects.Cela demandera à la comparaison de comparer tous les (collections de) types complexes auxquels les propriétés du sujet (dans cet exemple) font référence.Par défaut, il affirmera que les propriétés imbriquées du sujet correspondent aux propriétés imbriquées de l'objet attendu.Toutefois, si vous spécifiez SharedProperties, il comparera uniquement les propriétés de même nom entre les objets imbriqués.Par exemple:
dto.ShouldHave().SharedProperties().IncludingNestedObjects.EqualTo(customer);
"