2

The following is not asserting to true with XUnit (StartDate and EndDate are the only two public properties of DatePeriod):

var actual = new List<DatePeriod>()
{
    new DatePeriod() { StartDate = new DateTime(2017, 1, 20), EndDate = new DateTime(2018, 1, 19)},
    new DatePeriod() { StartDate = new DateTime(2018, 1, 20), EndDate = new DateTime(2018, 3, 31)}
};

var expected = new List<DatePeriod>()
{
    new DatePeriod() { StartDate = new DateTime(2017, 1, 20), EndDate = new DateTime(2018, 1, 19)},
    new DatePeriod() { StartDate = new DateTime(2018, 1, 20), EndDate = new DateTime(2018, 3, 31)}
};

Assert.Equal(actual, expected);

Based on some research I expected in the latest version of XUnit that these would end up being considered equal since when using Assert as long as the order is the same which it is.

Blake Rivell
  • 13,105
  • 31
  • 115
  • 231

2 Answers2

4

You simply need to override Equals and GetHashCode like this:

public class DatePeriod
{
    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;

        DatePeriod other = (DatePeriod)obj;

        return StartDate.Equals(other.StartDate) && EndDate.Equals(other.EndDate);
    }

    public override int GetHashCode()
    {
        return new {StartDate, EndDate}.GetHashCode();
    }

    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
}

xUnit recognizes collections in the sense that you can call Assert.Equal while other testing frameworks require special methods like CollectionAssert.AreEqual.

In all cases, the framework would invoke Equals for each item in the list passing the corresponding item from the other list. If you have a list of strings or integers then Equals is correctly implemented by default. For custom objects like DatePeriod, the default implementation of the Equals method is based on reference equality, i.e., two objects are equal is they are actually the same object. To get value based equality, you have to override the Equals method (and also GetHashCode method as recommended).

Yacoub Massad
  • 27,509
  • 2
  • 36
  • 62
  • Hey this seems to be exactly what I need, but can you look at the recent update I made to my post. I apologize because there is no such object as Period. Everything uses DatePeriod. – Blake Rivell Mar 28 '16 at 18:09
  • @BlakeRivell You originally had a List with a series of Period inside of it, which led Yacoub to believe Period subclassed DatePeriod. – Frozenthia Mar 28 '16 at 18:16
  • @TheAnathema Correct, which is why I tried letting him know about my mistake in the comments and asked if he could update his answer. – Blake Rivell Mar 28 '16 at 18:17
  • It is so strange how in this post they say Xunit recognizes collections so this is not necessary... http://stackoverflow.com/questions/419659/xunit-assert-two-listt-are-equal?rq=1 . What am I not understanding? – Blake Rivell Mar 28 '16 at 18:19
  • Should I be implementing IEquatable or IEqualityComparer? It seems like Yacoub is telling me to implement IEqualityComparer. – Blake Rivell Mar 28 '16 at 18:25
  • Yacoub I am trying to notify you that DatePeriod is not a complex object anymore it is flat. I updated my post. Can you please fix your answer for me? – Blake Rivell Mar 28 '16 at 18:26
  • For any custom object you have to override `Equals` and `GetHashCode`. These methods are defined on the `object` class. And we are simply overriding them in `DatePeriod`. – Yacoub Massad Mar 28 '16 at 18:28
  • Yacoub thank you for the extra explanation however you still are using Period in your answer. Period does not exist. If you fix this I will mark as correct. Please see my updated post, I had a typo initially. – Blake Rivell Mar 28 '16 at 18:31
  • I forgot to change this line. Updated now. – Yacoub Massad Mar 28 '16 at 18:32
  • Perfect I see it now. My final question is how come you aren't implementing IEqualityComparer? – Blake Rivell Mar 28 '16 at 18:32
  • Why would I need it? You usually do that if you want to create a custom comparer that can compare two instances of something and for some reason you don't want to put such comparison logic in the object itself (the one that you want to compare). – Yacoub Massad Mar 28 '16 at 18:35
  • When you say for some reason you don't want to put such comparison logic in the object itself are you referring to me and what I said? I am just trying to confirm that your final answer is exactly what I need since I have two lists of a flat object called DatePeriod that I want to compare.Is there any other way to do it? – Blake Rivell Mar 28 '16 at 18:37
  • 1
    No, sometimes I use it because I have a custom logic of comparison that is not intrinsic to the object itself. For example, I some cases I want to compare only by `ID` and not the rest of the properties. When you create a `Dictionary` for example, you can pass such custom comparer. – Yacoub Massad Mar 28 '16 at 18:39
  • 1
    If there is a "standard" way to compare the object, then you should put such comparison logic on the object itself. If the comparison logic is custom, you put it in a custom object (that implements `IEqualityComparer`). – Yacoub Massad Mar 28 '16 at 18:43
3

List<T> equality isn't checked element to element.

Use LINQ's SequenceEqual method to check your equality.

var equal = actual.SequenceEqual(expected);

And implement IEquatable on your object:

public class DatePeriod : IEquatable<DatePeriod>
{
     public DateTime StartDate { get; set; }
     public DateTime EndDate { get; set; }

     public bool Equals(Period other)
     {
         return StartDate == other.StartDate && EndDate == other.EndDate;
     }
}

For safety, check for nulls and what not. See Yacoub's answer for a more complete implementation.

Frozenthia
  • 759
  • 3
  • 9