5

To illustrate the problem, consider these three classes:

class Orange
{
    public String color { get; set; }
}
class Foo
{
    public Int32 size { get; set; }
    public Orange orange { get; set; }
}
class Bar
{
    public Int32 size { get; set; }
    public Orange orange { get; set; }
}

If I create instances of Foo and Bar, and set their Orange instance to be different, and then do an assertion (without using "Including") then the assertion works as expedcted; it finds the difference between "red" and "bff":

Foo foo = new Foo() { size = 3 };
foo.orange = new Orange() {color = "red" }; 

Bar bar = new Bar() { size = 3 };
bar.orange = new Orange() { color = "bff" };

foo.ShouldBeEquivalentTo(bar);  // assertion fails, as expected

But if I do the same assertion, while specifically telling FluentAssertions what to compare via "Including", the assertion doesn't catch the difference in Orange:

foo.ShouldBeEquivalentTo(bar, options => options
    .Including(o => o.size)
    .Including(o => o.orange)
    );   // assertion does not fail, why?
Hamid Pourjam
  • 20,441
  • 9
  • 58
  • 74
Michael Ray Lovett
  • 6,668
  • 7
  • 27
  • 36
  • I think I may have answered my own question - I think I need to do this instead: .Including(o => o.orange.color) – Michael Ray Lovett Mar 14 '16 at 16:57
  • What version did you use? – Dennis Doomen Mar 14 '16 at 17:06
  • 1
    4.2.2 My "fix" (ie o.orange.size) seems to work for simple cases. But right after I figured that out, I wanted to use Including to assert a field which holds an array of objects.. and the asserts didn't work for that. This could have something to do with the way I'm trying to Include the array: .Including(o => o.my_object.arraylist[0].field_of_interest). I didn't think entering the [0] felt right, but that's the only way I could get it to compile – Michael Ray Lovett Mar 14 '16 at 18:49

1 Answers1

3

You can use ComparingByValue

foo.ShouldBeEquivalentTo(bar, options => options
    .Including(o => o.size)
    .Including(o => o.orange)
    .ComparingByValue<Orange>());

Then it fails.

You have included orange property for comparing and comparing objects are based on their properties and you do not include any properties for orange to be compared so foo.orange is assumed to be equal to bar.orange.

Adding CompareByValue<Orange> makes comparing Orange instances based on their's Equals method. So by default it will make reference comparison.

You should override Equals for Orange

class Orange
{
    public String color { get; set; }

    public override bool Equals(object obj)
    {
        var orange = obj as Orange;
        return Equals(orange);
    }

    protected bool Equals(Orange other)
    {
        return other != null && 
            string.Equals(color, other.color);
    }

    public override int GetHashCode()
    {
        return color?.GetHashCode() ?? 0;
    }
}

Now you can compare even lists with this approach.

var foo = new Foo() { size = 3 };
var o1 = new Orange { color = "a" };
var o2 = new Orange { color = "a" };
var o3 = new Orange { color = "b" };
var o4 = new Orange { color = "b" };
foo.orange = new List<Orange>() { o1, o3 };

Bar bar = new Bar() { size = 3 };
bar.orange = new List<Orange>() { o1, o4 };

foo.ShouldBeEquivalentTo(bar);

foo.ShouldBeEquivalentTo(bar, options => options
    .Including(x => x.size)
    .Including(x => x.orange)
    .ComparingByValue<Orange>());
Hamid Pourjam
  • 20,441
  • 9
  • 58
  • 74
  • see my comment to original post. I figured out that specifying orange.color did work as expected, but I'm thinking your .ComparingByValue would cause all of orange to be compared, which is probably what most people would want. Also referring to my comment above, do you suppose ComparingByValue would work if orange were a list of objects instead of an object? – Michael Ray Lovett Mar 14 '16 at 18:54