How to implement unit tests on IEqualityComparer?

I have a class and a comparer for this class that implements IEqualityComparer:

class Foo
{
    public int Int { get; set; }
    public string Str { get; set; }

    public Foo(int i, string s)
    {
        Int = i;
        Str = s;
    }

    private sealed class FooEqualityComparer : IEqualityComparer<Foo>
    {
        public bool Equals(Foo x, Foo y)
        {
            if (ReferenceEquals(x, y)) return true;
            if (ReferenceEquals(x, null)) return false;
            if (ReferenceEquals(y, null)) return false;
            if (x.GetType() != y.GetType()) return false;
            return x.Int == y.Int && string.Equals(x.Str, y.Str);
        }

        public int GetHashCode(Foo obj)
        {
            unchecked
            {
                return (obj.Int * 397) ^ (obj.Str != null ? obj.Str.GetHashCode() : 0);
            }
        }
    }

    public static IEqualityComparer<Foo> Comparer { get; } = new FooEqualityComparer();
}

Two methods are Equalsalso GetHashCodeused, for example, List.Exceptthrough an instance of a comparator.

My question is: how to implement unit tests of this comparator correctly? I want to determine if someone is adding a public property to Foowithout changing the comparator, since in this case the comparator becomes invalid.

If I do something like:

Assert.That(new Foo(42, "answer"), Is.EqualTo(new Foo(42, "answer")));

It cannot detect that a new property has been added, and that this property is different from two objects.

Is there any way to do this?

If possible, can we add an attribute to the property to say that this property does not matter when comparing?

+4
3

, , :

var knownPropNames = new string[]
{
    "Int", 
    "Str", 
};
var props = typeof(Foo).GetProperties(BindingFlags.Public | BindingFlags.Instance);
var unknownProps = props
                    .Where(x => !knownPropNames.Contains(x.Name))
                    .Select(x => x.Name)
                    .ToArray();
// Use assertion instead of Console.WriteLine
Console.WriteLine("Unknown props: {0}", string.Join("; ", unknownProps));

, , , - . , . , , , .

BindingFlags, , .

, , , , . :

[AttributeUsage(AttributeTargets.Property)]
public class ComparerIgnoreAttribute : Attribute {}

:

[ComparerIgnore]
public decimal Dec { get; set; }

, , :

var unknownProps = props
                    .Where(x => !knownPropNames.Contains(x.Name) 
                        && !x.GetCustomAttributes(typeof(ComparerIgnoreAttribute)).Any())
                    .Select(x => x.Name)
                    .ToArray();
+1

, , Equals . , :

class Foo
{
    [MyAttribute]
    public string IgnoredProperty { get; set; }
    public string MyProperty { get; set; }
}

. , , PropertyInfo.GetValue

class MyComparer : IEqualityComparer<Foo>
{
    public bool Equals(Foo x, Foo y)
    {
        var properties = this.GetType().GetProperties()
                .Where(x => "Attribute.IsDefined(x, typeof(MyAttribute));
        var equal = true;
        foreach(var p in properties)
           equal &= p.GetValue(x, null) == p.GetValue(y, null);
        return equal;
    }
}

GetHashCode, .

EDIT: ReSharper, , , , R # GetHashCode. , , , . Equals.

EDIT2: , Equals GetHashCode - , . , , , . , , , , , .

+1

I think you can check the number of properties inside the comparator. Something like that:

private sealed class FooEqualityComparer : IEqualityComparer<Foo>
{
    private List<bool> comparisonResults = new List<bool>();
    private List<Func<Foo, Foo, bool>> conditions = new List<Func<Foo, Foo, bool>>{
        (x, y) => x.Int == y.Int,
        (x, y) => string.Equals(x.Str, y.Str)
    };
    private int propertiesCount = typeof(Foo)
                .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                //.Where(someLogicToExclde(e.g attribute))
                .Count();

    public bool Equals(Foo x, Foo y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (ReferenceEquals(x, null)) return false;
        if (ReferenceEquals(y, null)) return false;
        if (x.GetType() != y.GetType()) return false;   
        //has new property which is not presented in the conditions list and not excluded
        if (conditions.Count() != propertiesCount) return false;    

        foreach(var func in conditions)
            if(!func(x, y)) return false;//returns false on first mismatch

        return true;//only if all conditions are satisfied
    }

    public int GetHashCode(Foo obj)
    {
        unchecked
        {
            return (obj.Int * 397) ^ (obj.Str != null ? obj.Str.GetHashCode() : 0);
        }
    }
}
+1
source

Source: https://habr.com/ru/post/1680873/


All Articles