Alexey Shcherbak
Alexey Shcherbak

Reputation: 3454

How to compare two anonymous types or two collection of different types using SemanticComparison

1. Is there a simple way to compare two anonymous types using SemanticComparison from AutoFixture ? My current issue is that I can't construct Likeness for second anonymous object. Simplified example :

var srcAnon = new { time = expectedTime, data = docsArray };
var resultAnon = new { time=actualTime, data = sutResponseArray };

var expectedAlike = srcAnon.AsSource()
            .OfLikeness<??WhatsHere??>()

2. I think this question is pretty related to the first one as they both use SemanticComparison to create IEquatable implementations. In this question Mark Seemann provided an answer on how to do it using MSTest assertions and LINQ SequenceEqual method.

Is this possible to use XUnit2 assertions library in the similar scenario? XUnit supports Assert.Equal() for collections of the same type, can it be used for collections of different types, but if the elements implement IEquatable (using Likeness). Something like this (this doesn't work as result and allLikeness have different types):

Assert.Equal(allLikeness.ToArray(),result.ToArray());

Upvotes: 2

Views: 1591

Answers (1)

Mark Seemann
Mark Seemann

Reputation: 233150

Independently of any unit testing framework, you can always drop down to the SequenceEquals<object> overload that also takes a comparer. This will enable you to compare completely disparate lists. This test demonstrates how you can 'trick' .NET into 'thinking' that two heterogeneous arrays are identical:

[Fact]
public void TestDisparateSequences()
{
    var ints = new[] { 1, 3, 5, 7 };
    var strings = new[] { "foo", "bar", "baz", "qux" };

    Assert.True(
        ints.Cast<object>().SequenceEqual(
            strings.Cast<object>(),
            new TrueComparer<object>()),
        "Arrays look like they are equal.");
}

private class TrueComparer<T> : IEqualityComparer<T>
{
    public bool Equals(T x, T y)
    {
        return true;
    }

    public int GetHashCode(T obj)
    {
        return 0;
    }
}

This test passes, because TrueComparer always returns true.

Obviously, that's not particularly practical, but it points out the relevant building blocks one can use to compare heterogeneous sequences.

SemantiComparison provides a SemanticComparer<T> class that implements IEqualityComparer<T>, but it only works on values of the same type. Thus, in order to use it to compare heterogeneous sequences, you'll need to map one of the lists into a sequence of the other type.

Often, you'll already have such a map lying around, but if not, it's a good motivation for building one. Otherwise, you can use use a semantic mapper like AutoMapper.

Assume, as an example, that you have a Foo class like this:

public class Foo
{
    public int Number { get; set; }

    public string Text { get; set; }
}

and another Bar class, very similar:

public class Bar
{
    public int Number { get; set; }

    public string Text { get; set; }
}

You can now compare foos and bars using a map and SemanticComparison<Bar>:

[Fact]
public void TestEquivalentSequences()
{
    var foos = new[]
    {
        new Foo { Number = 42, Text = "ploeh" },
        new Foo { Number = 1337, Text = "fnaah" }
    };
    var bars = new[]
    {
        new Bar { Number = 42, Text = "ploeh" },
        new Bar { Number = 1337, Text = "fnaah" }
    };

    AutoMapper.Mapper.CreateMap<Foo, Bar>();

    Assert.True(
        foos.Select(AutoMapper.Mapper.Map<Bar>).SequenceEqual(
            bars,
            new SemanticComparer<Bar>()),
        "Mapped arrays should be equivalent.");
}

Still, it will make your life a whole lot easier if you give your objects Structural Equality. This answer only sketches out what's possible, not what's recommended.

Upvotes: 4

Related Questions