1

The Fluent Assertions library puts quite some emphasis on its capabilities of comparing object graphs by means of the Should().BeEquivalentTo method and related methods. As the documentation points out:

Should().BeEquivalentTo is a very powerful feature, and one of the unique selling points of Fluent Assertions.

From reading the examples, I am getting the impression that the object graph has to be connected by means of object references.

Now, I have plenty of object graphs and trees whose content I would like to check, but they are usually loaded from files or from databases. Therefore, the connection exists solely by ID property values that match across objects.

For instance, imagine this simple structure (here written in JSON format) - example A:

[{
    "Id": 1,
    "Name": "Foo",
    "NextId": 2
}, {
    "Id": 2,
    "Name": "Bar"
}]

I would like to use Fluent Assertions to check the structure of such an object graph. That is, no matter what values the fields Id and NextId actually have, NextId in the first object must have the same value as Id in the second object. Thus, any set of two objects with two different Id values, where one object has a NextId value that equals the Id value of the other object, will do.

Example B:

[{
    "Key": 5,
    "LinkedTo": [3]
}, {
    "Key": 10,
    "LinkedTo": []
}, {
    "Key": 3,
    "LinkedTo": [5]
}]

Here, any set of three objects with three different Key values, where two objects refer to each other's key in their LinkedTo property and the remaining object has an empty array in its LinkedTo property should match.

Example C:

[{
    "Id": 1,
    "Owner": 4
}, {
    "Id": 2,
    "Owner": 4
}, {
    "Id": 3,
    "Owner": 4
}, {
    "Id": 4
}]

Here, any set of four objects with three different Id values will match if three of those objects have an Owner property whose value matches the Id property of the remaining object.

Can this somehow be done with BeEquivalentTo?

Note that I do not want to walk through my objects and add actual object references before applying any assertions, because that operation in itself might introduce bugs of its own.


EDIT: As requested, here are a couple of matching and non-matching graphs for the above examples:

Example A:

Match:

[{
    "Id": 5,
    "Name": "Foo",
    "NextId": -8
}, {
    "Id": -8,
    "Name": "Bar"
}]

Match:

[{
    "Id": 200,
    "Name": "Bar"
}, {
    "Id": 30,
    "Name": "Foo",
    "NextId": 200
}]

Mismatch:

[{
    "Id": 3,
    "Name": "Bar",
    "NextId": 6
}, {
    "Id": 6,
    "Name": "Foo"
}]

Example B:

Match:

[{
    "Key": 20,
    "LinkedTo": [7]
}, {
    "Key": 8,
    "LinkedTo": []
}, {
    "Key": 7,
    "LinkedTo": [20]
}]

Match:

[{
    "Key": 9,
    "LinkedTo": [100]
}, {
    "Key": 100,
    "LinkedTo": [9]
}, {
    "Key": 3,
    "LinkedTo": []
}]

Mismatch:

[{
    "Key": 5,
    "LinkedTo": [10]
}, {
    "Key": 10,
    "LinkedTo": []
}, {
    "Key": 3,
    "LinkedTo": [5]
}]

Example C:

Match:

[{
    "Id": 1
}, {
    "Id": 2,
    "Owner": 1
}, {
    "Id": 3,
    "Owner": 1
}, {
    "Id": 4,
    "Owner": 1
}]

Match:

[{
    "Id": 10,
    "Owner": 20
}, {
    "Id": 30,
    "Owner": 20
}, {
    "Id": 20
}, {
    "Id": 40,
    "Owner": 20
}]

Mismatch:

[{
    "Id": 8,
    "Owner": 2
}, {
    "Id": 12,
    "Owner": 8
}, {
    "Id": 54,
    "Owner": 2
}, {
    "Id": 2
}]
F-H
  • 663
  • 1
  • 10
  • 21
  • What exactly do you want to assert is true about your data? `BeEquivalentTo` will assert that the collections have the same number of elements, and each element in `A` has a match in `B` where all properties have equivalent values (using this same logic of equivalency, recursively). Is a test failing where you expect it to pass? Can you share a full test of code? – JamesFaix Feb 28 '20 at 14:27
  • @JamesFaix: I do not know how to write the test code for this to start with. I want to assert that `NextId` in the first element has the same value as `Id` in the second element - no matter whether that value is 2, 5, or 369. I suppose a logic of equivalency would somehow include a mapping between IDs from my expected data and IDs found in the actual data. – F-H Feb 28 '20 at 14:32
  • `BeEquivalentTo` is most often used to assert a *subject*, e.g. the return value of a function against an *expected* value. Are you trying assert some invariant about your code? Could you provide a subject+expectation pair that should match, and another pair should fail matching? – Jonas Nyrup Mar 02 '20 at 18:42
  • @JonasNyrup: Done. (More verbosely: For each of the three examples of *expected* graphs, I have added two matching *subjects* and one *subject* for which the match should fail.) – F-H Mar 06 '20 at 10:09

1 Answers1

1

As I understand your question, you have a list of objects and want to assert that NextId and Id of each consecutive pair matches.

class MyClass
{
    public MyClass(int id, int nextId, string name)
    {
        Id = id;
        NextId = nextId;
        Name = name;
    }

    public int Id { get; set; }
    public int NextId { get; set; }
    public string Name { get; set; }
}

Fluent Assertions does not know about consecutiveness, instead we can create two lists such that each matching pair across the lists corresponds to consecutive items in the original list. We can now compare the two lists to see if each pair across the lists has the desired relationship.

To compare the two lists by NextId and Id we need to instruct Fluent Assertions how to compare two instances of MyClass.

For BeEquivalentTo you can specify how to compare to instances of a given type using the Using<T>() + WhenTypeIs<T>() combination. Note that per default will try to match the two lists in any order, but by specifying WithStrictOrdering() it will be pairwise comparison.

[TestMethod]
public void Lists_BeEquivalentTo()
{
    var objects = new[]
    {
        new MyClass(1, 2, "Foo"),
        new MyClass(2, 3, "Bar"),
        new MyClass(3, 4, "Baz")
    };

    objects.Take(objects.Length - 1).Should().BeEquivalentTo(objects.Skip(1), opt => opt
        .WithStrictOrdering()
        .Using<MyClass>(e => e.Subject.NextId.Should().Be(e.Expectation.Id))
        .WhenTypeIs<MyClass>());
}

If the comparison is as simple as comparing two ints, one could also use the Equal() assertion which just takes two lists and a predicate that specifies when two elements are equal.

[TestMethod]
public void Lists_Equal()
{
    var objects = new[]
    {
        new MyClass(1, 2, "Foo"),
        new MyClass(2, 3, "Bar"),
        new MyClass(3, 4, "Baz")
    };

    objects.Take(objects.Length - 1).Should().Equal(objects.Skip(1),
        (a, b) => a.NextId == b.Id);
}
Jonas Nyrup
  • 2,376
  • 18
  • 25
  • "and want to assert that NextId and Id of each consecutive pair matches" - no, sorry, that's not at all what I'm after. I wish to compare two object graphs, where the edges are not represented by .NET object references, but by matching ID values. I'll try to clarify my question. – F-H Feb 28 '20 at 15:34
  • I have provided some more exemplary object graphs for clarification. – F-H Feb 28 '20 at 15:42