2

I am trying to compare two list of objects with FluentAssertions. The objects have a property stored as a double that may be off by a small amount. Is there an efficient way to do this without iterating through the lists? My current method looks like

actualList.ShouldAllBeEquivalentTo(expectedList, options => options.Excluding(o => o.DoubleProperty));

for (var i = 0; i < actualList.Count; i++)
{
    actualList[i].DoubleProperty
                 .Should().BeApproximately(expectedList[i].DoubleProperty, precision);
}

Which is a little ugly and irritating as this issue keeps on coming up. Another possibility (inspired by Fluent Assertions: Compare two numeric collections approximately) is

actualList.Select(o => o.DoubleProperty)
          .Should().Equal(expectedList.Select(o => o.DoubleProperty),
                          (left, right) => AreEqualApproximately(left, right, precision));

Where I would write the AreEqualApproximately function myself. If possible, I would like to do the comparison without defining my own helper methods or iterating through the lists by index.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
yinnonsanders
  • 1,831
  • 11
  • 28
  • 2
    What's ugly about it? I mean, other than not having newline sequences to break up the dot chain? – hoodaticus Jul 21 '17 at 16:37
  • @hoodaticus In C#, I really shouldn't be iterating through lists by index. Beauty in code is something you really just have to sense. – yinnonsanders Jul 21 '17 at 16:47
  • 1
    You would need to transform your source data into a list of ValueTuples the first item of which is the actualList member and the second of which is the corresponding expectedList member. Then you would be able to do a List>.ForEach(i => i.Item1.DoubleProperty .Should().BeApproximately(i.Item2.DoubleProperty, precision)); The reason your code in its current form requires for loops by index is because it is having to manually correlate two separate lists. Put them in the same list and you are good. – hoodaticus Jul 21 '17 at 16:52
  • 1
    If you make that an answer I'll accept it. – yinnonsanders Jul 21 '17 at 16:58
  • 1
    Take a look at this https://stackoverflow.com/questions/36782975/fluent-assertions-approximately-compare-a-classes-properties – Nkosi Jul 21 '17 at 17:06
  • @Nkosi that looks even better, as long as it also works for `ShouldAllBeEquivalentTo` – yinnonsanders Jul 21 '17 at 17:13
  • @yinnonsanders check provided answer – Nkosi Jul 21 '17 at 17:26

3 Answers3

2

The following should also work using the options available in ShouldAllBeEquivalentTo

actualList.ShouldAllBeEquivalentTo(expectedList, options => options
    .Using<double>(ctx => ctx.Subject.Should()
                             .BeApproximately(ctx.Expectation, precision))
    .When(o => o.SelectedMemberPath == "DoubleProperty"));
Nkosi
  • 235,767
  • 35
  • 427
  • 472
1

You can create extension methods that will merge your actual and expected values into a single list and foreach over them:

public static class ExtensionMethods
{
    public static IEnumerable<ValueTuple<T, T>> Merge<T>(this List<T> a, List<T> b)
    {
        for (int x = 0, y = 0; x < a.Count && y < a.Count; x++, y++) 
        {
            yield return ValueTuple.Create(a[x], b[y]);
        }
    }

    public static void ForEach<T>(this IEnumerable<T> s, Action<T> m)
    {
       foreach (var i in s) m(i);
    }
}

Then, you can use it like this:

actualList.Merge(expectedList)
   .ForEach(i => 
   i.Item1.DoubleProperty
   .Should().BeApproximately(i.Item2.DoubleProperty, precision)); 
hoodaticus
  • 3,772
  • 1
  • 18
  • 28
1

Based on Fluent Assertions: Approximately compare a classes properties

actualList.ShouldAllBeEquivalentTo(
    expectedList,
    options => options.Using<double>(d => d.Subject.Should().BeApproximately(d.Expectation, precision))
                      .WhenTypeIs<double>()

Turns out to work the best for me, although because I have to do this several times, I ended up changing the options for FluentAssertions globally in TestInitialize.

yinnonsanders
  • 1,831
  • 11
  • 28