ligaoren
ligaoren

Reputation: 1063

How to Compare two objects in unit test?

public class Student
{
    public string Name { get; set; }
    public int ID { get; set; }
}

...

var st1 = new Student
{
    ID = 20,
    Name = "ligaoren",
};

var st2 = new Student
{
    ID = 20,
    Name = "ligaoren",
};

Assert.AreEqual<Student>(st1, st2);// How to Compare two object in Unit test?

How to Compare two collection in Unitest?

Upvotes: 65

Views: 91492

Answers (17)

MiBol
MiBol

Reputation: 2125

In .Net 6 it's recommended use System.Text.Json to Serialize any object to string.

And in this way, you will not need any additional NuGet package.

The functions GetConfiguration and GetMock, both, return an instance of the same object.

Example wrote in XUnit - But the core code should work in any test project:

[Fact]
public void CompareConfig()
{
    var response = GetConfiguration();
    var mock = GetMock();

    string result = JsonSerializer.Serialize(response);
    string expectedResult = JsonSerializer.Serialize(mock);

    Assert.Equal(expectedResult, result);
}

Upvotes: 0

Martin
Martin

Reputation: 5623

I would like to add another version of the answer posted by Mark Seeman.

In my code I use Mark Seemans SemanticComparison Nuget package (In the past this Nuget package was part of the AutoFixture package, but now it exists independently.)

This package allows to write code like in the "Assert" section in the example below where I compare the actualInstance with the expectedInstance that are both of type DataClass.

In the example I added an optional step to exclude the ID property when the two instances of the DataClass are compared using the Without method.

    [Test]
    public void CompareInstances_Test()
    {
        // Arrange: Instanciate a class the represents the instance with the properties you expect
        DataClass expectedInstance = new DataClass();
        // Set properties here


        // Act: Call a method in the tested class that will produce the result you want to compare to
        var subjectUnderTest = new SubjectUnderTest();
        DataClass actualInstance = subjectUnderTest.CreateInstance();


        // Assert: Compare the two instances, but ignore the "ID" property
        expectedInstance.AsSource().OfLikeness<DataClass>().Without(x => x.ID).ShouldEqual(actualInstance);
    }

Upvotes: 1

Babar Shamsi
Babar Shamsi

Reputation: 93

Use simply this method, it will check and returns successful results if your class instances are same. Not your class reference types. Cheers :)

private fun verifyYourObject(obj1: YourClass, clazz: Class<*>) {
        assertNotNull(obj1)
        assertThat(obj1, instanceOf(clazz))
    }

Upvotes: 0

Mark Seemann
Mark Seemann

Reputation: 233125

What you are looking for is what in xUnit Test Patterns is called Test-Specific Equality.

While you can sometimes choose to override the Equals method, this may lead to Equality Pollution because the implementation you need to the test may not be the correct one for the type in general.

For example, Domain-Driven Design distinguishes between Entities and Value Objects, and those have vastly different equality semantics.

When this is the case, you can write a custom comparison for the type in question.

If you get tired doing this, AutoFixture's Likeness class offers general-purpose Test-Specific Equality. With your Student class, this would allow you to write a test like this:

[TestMethod]
public void VerifyThatStudentAreEqual()
{
    Student st1 = new Student();
    st1.ID = 20;
    st1.Name = "ligaoren";

    Student st2 = new Student();
    st2.ID = 20;
    st2.Name = "ligaoren";

    var expectedStudent = new Likeness<Student, Student>(st1);

    Assert.AreEqual(expectedStudent, st2);
}

This doesn't require you to override Equals on Student.

Likeness performs a semantic comparison, so it can also compare two different types as long as they are semantically similar.

Upvotes: 67

Mehran
Mehran

Reputation: 2027

You can also use NFluent with the this syntax to deep compare two objects without implementing equality for your objects. NFluent is a library that tries to simplify writing readable test code.

Check.That(actual).HasFieldsWithSameValues(expected);

This method with fail with an exception containing all the differences instead of failing at the first one. I find this feature to be a plus.

Upvotes: 3

Marc L.
Marc L.

Reputation: 3399

Mark Seeman's answer covers the general issue: that test-equality is a separate concern, so the code therefore should be external to the class itself. (I haven't seen "equality pollution" before, but that). Also, it is a concern that is isolated to you unit-test project. Even better, it is in many cases a "solved problem": there are any number of assertion libraries available that will allow you to test equality in any number of arbitrary ways. He suggests one, though there are many which have sprung up or become much more mature in the intervening years.

To that end, let me suggest Fluent Assertions. It has many capabilities for all manner of comparisons. In this case it would be very simple:

st1.ShouldBeEquivalentTo(st2); // before 5.0

or

st1.Should().BeEquivalentTo(st2); // 5.0 and later

Upvotes: 8

Ferhat KO&#199;ER
Ferhat KO&#199;ER

Reputation: 4065

  1. Hello firstly add your test project Newtonsoft.Json with Nuget PM

    PM> Install-Package Newtonsoft.Json -Version 10.0.3

  2. Then add test file

    using Newtonsoft.Json;
    
  3. Usage:

    Assert.AreEqual(
        JsonConvert.SerializeObject(expected),
        JsonConvert.SerializeObject(actual));
    

Upvotes: 4

Jason Morse
Jason Morse

Reputation: 6304

It looked liked AutoFixture's Likeness is what I needed for this problem (thanks Mark Seeman) however it does not support comparing collection elements for likeness (there's a couple open issues on the matter but they have not been resolved).

I found CompareObjects by Kellerman Software does the trick:

https://github.com/GregFinzer/Compare-Net-Objects

Upvotes: 7

cnom
cnom

Reputation: 3241

I just did:

Assert.AreEqual(Newtonsoft.Json.JsonConvert.SerializeObject(object1),
                Newtonsoft.Json.JsonConvert.SerializeObject(object2));

Upvotes: 4

Martin
Martin

Reputation: 5623

If you use NUnit you may using this syntax and specify an IEqualityComparer specifically for the test:

[Test]
public void CompareObjectsTest()
{
    ClassType object1 = ...;
    ClassType object2 = ...;
    Assert.That( object1, Is.EqualTo( object2 ).Using( new MyComparer() ) );
}

private class MyComparer : IEqualityComparer<ClassType>
{
    public bool Equals( ClassType x, ClassType y )
    {
        return ....
    }

    public int GetHashCode( ClassType obj )
    {
        return obj.GetHashCode();
    }
}

See also here: Equal Constraint (NUnit 2.4 / 2.5)

Upvotes: 1

Alex
Alex

Reputation: 1160

obj1.ToString().Equals(obj2.ToString())

Upvotes: -4

John Weisz
John Weisz

Reputation: 31924

If comparing public members is enough for your use-case, simply jam your objects into JSON and compare the resulting strings:

var js = new JavaScriptSerializer();
Assert.AreEqual(js.Serialize(st1), js.Serialize(st2));

JavaScriptSerializer Class

Pros

  • Requires minimal code, zero effort, and no preliminary setup
  • Handles complex structures with nested objects
  • Does not pollute your types with unit test-specific code, like Equals

Cons

  • Only serializable, public members are considered (no need to annotate your members, though)
  • Does not handle circular references

Upvotes: 17

ShloEmi
ShloEmi

Reputation: 1984

This is what I do:

public static void AreEqualXYZ_UsageExample()
{
    AreEqualXYZ(actual: class1UnderTest, 
        expectedBoolExample: true, 
        class2Assert: class2 => Assert.IsNotNull(class2), 
        class3Assert: class3 => Assert.AreEqual(42, class3.AnswerToEverything));
}

public static void AreEqualXYZ(Class1 actual,
    bool expectedBoolExample,
    Action<Class2> class2Assert,
    Action<Class3> class3Assert)
{
    Assert.AreEqual(actual.BoolExample, expectedBoolExample);

    class2Assert(actual.Class2Property);
    class3Assert(actual.Class3Property);
}

HTH..

Upvotes: 0

ligaoren
ligaoren

Reputation: 1063

http://www.infoq.com/articles/Equality-Overloading-DotNET

This article may be usefull , I solve this problem just using refcetion dump all filed out; Then we just need compare two strings.

Code Here:

 /// <summary>
    /// output all properties and values of obj
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="separator">default as ";"</param>
    /// <returns>properties and values of obj,with specified separator </returns>
    /// <Author>ligaoren</Author>
    public static string Dump(object obj, string separator)
    {
        try
        {
            if (obj == null)
            {
                return string.Empty;
            }
            if (string.IsNullOrEmpty(separator))
            {
                separator = ";";
            }
            Type t = obj.GetType();
            StringBuilder info = new StringBuilder(t.Name).Append(" Values : ");
            foreach (PropertyInfo item in t.GetProperties())
            {
                object value = t.GetProperty(item.Name).GetValue(obj, null);
                info.AppendFormat("[{0}:{1}]{2}", item.Name, value, separator);
            }
            return info.ToString();
        }
        catch (Exception ex)
        {
            log.Error("Dump Exception", ex);
            return string.Empty;
        }
    }

Upvotes: 4

Samuel Neff
Samuel Neff

Reputation: 74899

Here's an NUnit 2.4.6 custom constraint we use for comparing complex graphs. It supports embedded collections, parent references, setting tolerance for numeric comparisons, identifying field names to ignore (even deep within the hierarchy), and decorating types to be always ignored.

I'm sure this code can be adapted to be used outside NUnit, the bulk of the code isn't dependent on NUnit.

We use this in thousands of unit tests.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using NUnit.Framework;
using NUnit.Framework.Constraints;

namespace Tests
{
    public class ContentsEqualConstraint : Constraint
    {
        private readonly object expected;
        private Constraint failedEquality;
        private string expectedDescription;
        private string actualDescription;

        private readonly Stack<string> typePath = new Stack<string>();
        private string typePathExpanded;

        private readonly HashSet<string> _ignoredNames = new HashSet<string>();
        private readonly HashSet<Type> _ignoredTypes = new HashSet<Type>();
        private readonly LinkedList<Type> _ignoredInterfaces = new LinkedList<Type>();
        private readonly LinkedList<string> _ignoredSuffixes = new LinkedList<string>();
        private readonly IDictionary<Type, Func<object, object, bool>> _predicates = new Dictionary<Type, Func<object, object, bool>>();

        private bool _withoutSort;
        private int _maxRecursion = int.MaxValue;

        private readonly HashSet<VisitedComparison> _visitedObjects = new HashSet<VisitedComparison>();

        private static readonly HashSet<string> _globallyIgnoredNames = new HashSet<string>();
        private static readonly HashSet<Type> _globallyIgnoredTypes = new HashSet<Type>();
        private static readonly LinkedList<Type> _globallyIgnoredInterfaces = new LinkedList<Type>();

        private static object _regionalTolerance;

        public ContentsEqualConstraint(object expectedValue)
        {
            expected = expectedValue;
        }

        public ContentsEqualConstraint Comparing<T>(Func<T, T, bool> predicate)
        {
            Type t = typeof (T);

            if (predicate == null)
            {
                _predicates.Remove(t);
            }
            else
            {
                _predicates[t] = (x, y) => predicate((T) x, (T) y);
            }
            return this;
        }

        public ContentsEqualConstraint Ignoring(string fieldName)
        {
            _ignoredNames.Add(fieldName);
            return this;
        }

        public ContentsEqualConstraint Ignoring(Type fieldType)
        {
            if (fieldType.IsInterface)
            {
                _ignoredInterfaces.AddFirst(fieldType);
            }
            else
            {
                _ignoredTypes.Add(fieldType);
            }
            return this;
        }

        public ContentsEqualConstraint IgnoringSuffix(string suffix)
        {
            if (string.IsNullOrEmpty(suffix))
            {
                throw new ArgumentNullException("suffix");
            }
            _ignoredSuffixes.AddLast(suffix);
            return this;
        }

        public ContentsEqualConstraint WithoutSort()
        {
            _withoutSort = true;
            return this;
        }

        public ContentsEqualConstraint RecursingOnly(int levels)
        {
            _maxRecursion = levels;
            return this;
        }

        public static void GlobalIgnore(string fieldName)
        {
            _globallyIgnoredNames.Add(fieldName);
        }

        public static void GlobalIgnore(Type fieldType)
        {
            if (fieldType.IsInterface)
            {
                _globallyIgnoredInterfaces.AddFirst(fieldType);
            }
            else
            {
                _globallyIgnoredTypes.Add(fieldType);
            }
        }

        public static IDisposable RegionalIgnore(string fieldName)
        {
            return new RegionalIgnoreTracker(fieldName);
        }

        public static IDisposable RegionalIgnore(Type fieldType)
        {
            return new RegionalIgnoreTracker(fieldType);
        }

        public static IDisposable RegionalWithin(object tolerance)
        {
            return new RegionalWithinTracker(tolerance);
        }

        public override bool Matches(object actualValue)
        {
            typePathExpanded = null;
            actual = actualValue;
            return Matches(expected, actualValue);
        }

        private bool Matches(object expectedValue, object actualValue)
        {

            bool matches = true;

            if (!MatchesNull(expectedValue, actualValue, ref matches))
            {
                return matches;
            }
            // DatesEqualConstraint supports tolerance in dates but works as equal constraint for everything else
            Constraint eq = new DatesEqualConstraint(expectedValue).Within(tolerance ?? _regionalTolerance);
            if (eq.Matches(actualValue))
            {
                return true;
            }

            if (MatchesVisited(expectedValue, actualValue, ref matches))
            {
                if (MatchesDictionary(expectedValue, actualValue, ref matches) &&
                    MatchesList(expectedValue, actualValue, ref matches) &&
                    MatchesType(expectedValue, actualValue, ref matches) &&
                    MatchesPredicate(expectedValue, actualValue, ref matches))
                {
                    MatchesFields(expectedValue, actualValue, eq, ref matches);
                }
            }

            return matches;
        }

        private bool MatchesNull(object expectedValue, object actualValue, ref bool matches)
        {
            if (IsNullEquivalent(expectedValue))
            {
                expectedValue = null;
            }

            if (IsNullEquivalent(actualValue))
            {
                actualValue = null;
            }

            if (expectedValue == null && actualValue == null)
            {
                matches = true;
                return false;
            }

            if (expectedValue == null)
            {
                expectedDescription = "null";
                actualDescription = "NOT null";
                matches = Failure;
                return false;
            }

            if (actualValue == null)
            {
                expectedDescription = "not null";
                actualDescription = "null";
                matches = Failure;
                return false;
            }

            return true;
        }

        private bool MatchesType(object expectedValue, object actualValue, ref bool matches)
        {
            Type expectedType = expectedValue.GetType();
            Type actualType = actualValue.GetType();

            if (expectedType != actualType)
            {
                try
                {
                    Convert.ChangeType(actualValue, expectedType);
                }
                catch(InvalidCastException)             
                {
                    expectedDescription = expectedType.FullName;
                    actualDescription = actualType.FullName;
                    matches = Failure;
                    return false;
                }

            }
            return true;
        }

        private bool MatchesPredicate(object expectedValue, object actualValue, ref bool matches)
        {
            Type t = expectedValue.GetType();
            Func<object, object, bool> predicate;

            if (_predicates.TryGetValue(t, out predicate))
            {
                matches = predicate(expectedValue, actualValue);
                return false;
            }
            return true;
        }

        private bool MatchesVisited(object expectedValue, object actualValue, ref bool matches)
        {
            var c = new VisitedComparison(expectedValue, actualValue);

            if (_visitedObjects.Contains(c))
            {
                matches = true;
                return false;
            }

            _visitedObjects.Add(c);

            return true;
        }

        private bool MatchesDictionary(object expectedValue, object actualValue, ref bool matches)
        {
            if (expectedValue is IDictionary && actualValue is IDictionary)
            {
                var expectedDictionary = (IDictionary)expectedValue;
                var actualDictionary = (IDictionary)actualValue;

                if (expectedDictionary.Count != actualDictionary.Count)
                {
                    expectedDescription = expectedDictionary.Count + " item dictionary";
                    actualDescription = actualDictionary.Count + " item dictionary";
                    matches = Failure;
                    return false;
                }

                foreach (DictionaryEntry expectedEntry in expectedDictionary)
                {
                    if (!actualDictionary.Contains(expectedEntry.Key))
                    {
                        expectedDescription = expectedEntry.Key + " exists";
                        actualDescription = expectedEntry.Key + " does not exist";
                        matches = Failure;
                        return false;
                    }
                    if (CanRecurseFurther)
                    {
                        typePath.Push(expectedEntry.Key.ToString());
                        if (!Matches(expectedEntry.Value, actualDictionary[expectedEntry.Key]))
                        {
                            matches = Failure;
                            return false;
                        }
                        typePath.Pop();
                    }
                }
                matches = true;
                return false;
            }
            return true;
        }

        private bool MatchesList(object expectedValue, object actualValue, ref bool matches)
        {
            if (!(expectedValue is IList && actualValue is IList))
            {
                return true;
            }

            var expectedList = (IList) expectedValue;
            var actualList = (IList) actualValue;

            if (!Matches(expectedList.Count, actualList.Count))
            {
                matches = false;
            }
            else
            {
                if (CanRecurseFurther)
                {
                    int max = expectedList.Count;

                    if (max != 0 && !_withoutSort)
                    {
                        SafeSort(expectedList);
                        SafeSort(actualList);
                    }

                    for (int i = 0; i < max; i++)
                    {
                        typePath.Push(i.ToString());

                        if (!Matches(expectedList[i], actualList[i]))
                        {
                            matches = false;
                            return false;
                        }
                        typePath.Pop();
                    }
                }
                matches = true;
            }
            return false;
        }

        private void MatchesFields(object expectedValue, object actualValue, Constraint equalConstraint, ref bool matches)
        {
            Type expectedType = expectedValue.GetType();

            FieldInfo[] fields = expectedType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);

            // should have passed the EqualConstraint check
            if (expectedType.IsPrimitive ||
                expectedType == typeof(string) ||
                expectedType == typeof(Guid) ||
                fields.Length == 0)
            {
                failedEquality = equalConstraint;
                matches = Failure;
                return;
            }

            if (expectedType == typeof(DateTime))
            {
                var expectedDate = (DateTime)expectedValue;
                var actualDate = (DateTime)actualValue;

                if (Math.Abs((expectedDate - actualDate).TotalSeconds) > 3.0)
                {
                    failedEquality = equalConstraint;
                    matches = Failure;
                    return;
                }
                matches = true;
                return;
            }

            if (CanRecurseFurther)
            {
                while(true)
                {
                    foreach (FieldInfo field in fields)
                    {
                        if (!Ignore(field))
                        {
                            typePath.Push(field.Name);
                            if (!Matches(GetValue(field, expectedValue), GetValue(field, actualValue)))
                            {
                                matches = Failure;
                                return;
                            }
                            typePath.Pop();
                        }
                    }
                    expectedType = expectedType.BaseType;
                    if (expectedType == null)
                    {
                        break;
                    }
                    fields = expectedType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
                }
            }
            matches = true;
            return;
        }

        private bool Ignore(FieldInfo field)
        {
            if (_ignoredNames.Contains(field.Name) ||
                _ignoredTypes.Contains(field.FieldType) ||
                _globallyIgnoredNames.Contains(field.Name) ||
                _globallyIgnoredTypes.Contains(field.FieldType) ||
                field.GetCustomAttributes(typeof (IgnoreContentsAttribute), false).Length != 0)
            {
                return true;
            }

            foreach(string ignoreSuffix in _ignoredSuffixes)
            {
                if (field.Name.EndsWith(ignoreSuffix))
                {
                    return true;
                }
            }

            foreach (Type ignoredInterface in _ignoredInterfaces)
            {
                if (ignoredInterface.IsAssignableFrom(field.FieldType))
                {
                    return true;
                }
            }
            return false;
        }

        private static bool Failure
        {
            get
            {
                return false;
            }
        }

        private static bool IsNullEquivalent(object value)
        {
            return value == null ||
                    value == DBNull.Value ||
                   (value is int && (int) value == int.MinValue) ||
                   (value is double && (double) value == double.MinValue) ||
                   (value is DateTime && (DateTime) value == DateTime.MinValue) ||
                   (value is Guid && (Guid) value == Guid.Empty) ||
                   (value is IList && ((IList)value).Count == 0);
        }

        private static object GetValue(FieldInfo field, object source)
        {
            try
            {
                return field.GetValue(source);
            }
            catch(Exception ex)
            {
                return ex;
            }
        }

        public override void WriteMessageTo(MessageWriter writer)
        {
            if (TypePath.Length != 0)
            {
                writer.WriteLine("Failure on " + TypePath);
            }

            if (failedEquality != null)
            {
                failedEquality.WriteMessageTo(writer);
            }
            else
            {
                base.WriteMessageTo(writer);
            }
        }
        public override void WriteDescriptionTo(MessageWriter writer)
        {
            writer.Write(expectedDescription);
        }

        public override void WriteActualValueTo(MessageWriter writer)
        {
            writer.Write(actualDescription);
        }

        private string TypePath
        {
            get
            {
                if (typePathExpanded == null)
                {
                    string[] p = typePath.ToArray();
                    Array.Reverse(p);
                    var text = new StringBuilder(128);
                    bool isFirst = true;
                    foreach(string part in p)
                    {
                        if (isFirst)
                        {
                            text.Append(part);
                            isFirst = false;
                        }
                        else
                        {
                            int i;
                            if (int.TryParse(part, out i))
                            {
                                text.Append("[" + part + "]");
                            }
                            else
                            {
                                text.Append("." + part);
                            }
                        }
                    }
                    typePathExpanded = text.ToString();
                }
                return typePathExpanded;
            }
        }

        private bool CanRecurseFurther
        {
            get
            {
                return typePath.Count < _maxRecursion;
            }
        }

        private static bool SafeSort(IList list)
        {
            if (list == null)
            {
                return false;
            }

            if (list.Count < 2)
            {
                return true;
            }

            try
            {
                object first = FirstNonNull(list) as IComparable;
                if (first == null)
                {
                    return false;
                }

                if (list is Array)
                {
                    Array.Sort((Array)list);
                    return true;
                }
                return CallIfExists(list, "Sort");
            }
            catch
            {
                return false;
            }
        }

        private static object FirstNonNull(IEnumerable enumerable)
        {
            if (enumerable == null)
            {
                throw new ArgumentNullException("enumerable");
            }
            foreach (object item in enumerable)
            {
                if (item != null)
                {
                    return item;
                }
            }
            return null;
        }

        private static bool CallIfExists(object instance, string method)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            if (String.IsNullOrEmpty(method))
            {
                throw new ArgumentNullException("method");
            }
            Type target = instance.GetType();
            MethodInfo m = target.GetMethod(method, new Type[0]);
            if (m != null)
            {
                m.Invoke(instance, null);
                return true;
            }
            return false;
        }

        #region VisitedComparison Helper

        private class VisitedComparison
        {
            private readonly object _expected;
            private readonly object _actual;

            public VisitedComparison(object expected, object actual)
            {
                _expected = expected;
                _actual = actual;
            }

            public override int GetHashCode()
            {
                return GetHashCode(_expected) ^ GetHashCode(_actual);
            }

            private static int GetHashCode(object o)
            {
                if (o == null)
                {
                    return 0;
                }
                return o.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                if (obj == null)
                {
                    return false;
                }

                if (obj.GetType() != typeof(VisitedComparison))
                {
                    return false;
                }

                var other = (VisitedComparison) obj;
                return _expected == other._expected &&
                       _actual == other._actual;
            }
        }

        #endregion

        #region RegionalIgnoreTracker Helper

        private class RegionalIgnoreTracker : IDisposable
        {
            private readonly string _fieldName;
            private readonly Type _fieldType;

            public RegionalIgnoreTracker(string fieldName)
            {
                if (!_globallyIgnoredNames.Add(fieldName))
                {
                    _globallyIgnoredNames.Add(fieldName);
                    _fieldName = fieldName;
                }
            }

            public RegionalIgnoreTracker(Type fieldType)
            {
                if (!_globallyIgnoredTypes.Add(fieldType))
                {
                    _globallyIgnoredTypes.Add(fieldType);
                    _fieldType = fieldType;
                }
            }

            public void Dispose()
            {
                if (_fieldName != null)
                {
                    _globallyIgnoredNames.Remove(_fieldName);
                }
                if (_fieldType != null)
                {
                    _globallyIgnoredTypes.Remove(_fieldType);
                }
            }
        }

        #endregion

        #region RegionalWithinTracker Helper

        private class RegionalWithinTracker : IDisposable
        {
            public RegionalWithinTracker(object tolerance)
            {
                _regionalTolerance = tolerance;
            }

            public void Dispose()
            {
                _regionalTolerance = null;
            }
        }

        #endregion

        #region IgnoreContentsAttribute

        [AttributeUsage(AttributeTargets.Field)]
        public sealed class IgnoreContentsAttribute : Attribute
        {
        }

        #endregion
    }
    public class DatesEqualConstraint : EqualConstraint
    {
        private readonly object _expected;

        public DatesEqualConstraint(object expectedValue) : base(expectedValue)
        {
            _expected = expectedValue;
        }

        public override bool Matches(object actualValue)
        {
            if (tolerance != null && tolerance is TimeSpan)
            {
                if (_expected is DateTime && actualValue is DateTime)
                {
                    var expectedDate = (DateTime) _expected;
                    var actualDate = (DateTime) actualValue;
                    var toleranceSpan = (TimeSpan) tolerance;

                    if ((actualDate - expectedDate).Duration() <= toleranceSpan)
                    {
                        return true;
                    }
                }
                tolerance = null;
            }
            return base.Matches(actualValue);
        }
    }
}

Upvotes: 7

jason
jason

Reputation: 241583

You should provide an override of Object.Equals and Object.GetHashCode:

public override bool Equals(object obj) {
    Student other = obj as Student;
    if(other == null) {
        return false;
    }
    return (this.Name == other.Name) && (this.ID == other.ID);
}

public override int GetHashCode() {
    return 33 * Name.GetHashCode() + ID.GetHashCode();
}

As for checking if two collections are equal, use Enumerable.SequenceEqual:

// first and second are IEnumerable<T>
Assert.IsTrue(first.SequenceEqual(second)); 

Note that you might need to use the overload that accepts an IEqualityComparer<T>.

Upvotes: 8

tster
tster

Reputation: 18237

Maybe you need to add an public bool Equals(object o) to the class.

Upvotes: -1

Related Questions