42

In one of my tests, I want to ensure that a collection has certain items. Therefore, I want to compare this collection with the items of an expected collection not regarding the order of the items. Currently, my test code looks somewhat like this:

[Fact]
public void SomeTest()
{
    // Do something in Arrange and Act phase to obtain a collection
    List<int> actual = ...

    // Now the important stuff in the Assert phase
    var expected = new List<int> { 42, 87, 30 };
    Assert.Equal(expected.Count, actual.Count);
    foreach (var item in actual)
        Assert.True(expected.Contains(item));
}

Is there any easier way to achieve this in xunit.net? I can't use Assert.Equal as this method checks if the order of the items is the same in both collections. I had a look at Assert.Collection but that doesn't remove the Assert.Equal(expected.Count, actual.Count) statement in the code above.

Luke Girvin
  • 13,221
  • 9
  • 64
  • 84
feO2x
  • 5,358
  • 2
  • 37
  • 46

6 Answers6

35

Brad Wilson from xunit.net told me in this Github Issue that one should use LINQ's OrderBy operator and afterwards Assert.Equal to verify that two collections contain equal items without regarding their order. Of course, you would have to have a property on the corresponding item class that you can use for ordering in the first place (which I didn't really have in my case).

Personally, I solved this problem by using FluentAssertions, a library that provides a lot of assertion methods that can be applied in a fluent style. Of course, there are also a lot of methods that you can use to validate collections.

In the context of my question, I would use something like the following code:

[Fact]
public void Foo()
{
    var first = new[] { 1, 2, 3 };
    var second = new[] { 3, 2, 1 };

    first.Should().BeEquivalentTo(second);
}

This test passes because the BeEquivalentTo call ignores the order of the items.

Shouldly is also a good alternative if you do not want to go with FluentAssertions.

feO2x
  • 5,358
  • 2
  • 37
  • 46
17

Not a Xunit, but a Linq answer :

bool areSame = !expected.Except(actual).Any() && expected.Count == actual.Count;

So in XUnit :

Assert.True(!expected.Except(actual).Any() && expected.Count == actual.Count));

As @robi-y said, in Microsoft.VisualStudio.QualityTools.UnitTestFramework there is CollectionAssert.AreEquivalent

rducom
  • 7,072
  • 1
  • 25
  • 39
  • 1
    I think you're right @aquinas: Raph's answer will fail if list 1 is {1, 3, 5} and list 2 is {1, 3, 3, 3, 5}. Now that I think of it, checking sizes may not be enough because that would fail if list 1 is {1, 1, 3, 5, 5} and list 2 is {1, 3, 3, 5, 5}. Still, +1 to Raph for a very elegant starting point. – Ed Gibbs Jan 29 '15 at 15:34
  • Except yields the differences between two lists. If there are an item more in one list, this item will be yielded. So no need to check sizes. – rducom Jan 29 '15 at 15:35
  • Hmmm. Yes, if there is an overlap of values, it fails. Moreover, this test does'n' check values order. You have to add check on Count. So I correct my answer – rducom Jan 29 '15 at 15:35
  • Thanks for your answer, Raph. It works but it is not exactly what I'm looking for. The important thing is that if that very test would fail, it just would state "condition is not true" in the error message, which does not explain which elements are in the expected collection that are not in the actual one (and vice versa). This should be included in the exception message that is thrown, otherwise I have to debug the test to get this information. I will therefore create my own Assert method and try to contribute it to xunit.net. – feO2x Jan 29 '15 at 16:15
  • As the function expected.Except(actual) returns the difference between both lists, you can decompose the Assertion : First you check the result of Except, and if not empty, then you throw an exception with the content of the Except result. Then you continue with the rest of the test. – rducom Jan 29 '15 at 16:18
  • 3
    You might also use the [CollectionAssert.AreEquivalent](https://msdn.microsoft.com/en-us/library/ms243779.aspx) from mstest... – robi-y Feb 01 '15 at 13:01
  • 2
    Just as a warning that I came across playing with this. Except doesn't yield the differences per se. It filters anything out of the first list that appears in the second. So, if you have expected = {1,2,3,4,4} and actual = {1,2,3,4,5}, the assertion will pass incorrectly. So (if you're not going to use CollectionAssert), you should test the Except both ways, i.e. `(!expected.Except(actual).Any() && !actual.Except(expected).Any())` – YakkoWarner Sep 05 '19 at 18:32
10

Maybe another way is:

Assert.True(expected.SequenceEqual(actual));

This does checks the order too. This is what happens internally:

using (IEnumerator<TSource> e1 = first.GetEnumerator())
using (IEnumerator<TSource> e2 = second.GetEnumerator())
{
    while (e1.MoveNext())
    {
        if (!(e2.MoveNext() && comparer.Equals(e1.Current, e2.Current))) return false;
    }
    if (e2.MoveNext()) return false;
}
return true;

So if you don't care about the order, just order both lists before:

Assert.True(expected.OrderBy(i => i).SequenceEqual(actual.OrderBy(i => i)));
Lauren Rutledge
  • 1,195
  • 5
  • 18
  • 27
rducom
  • 7,072
  • 1
  • 25
  • 39
  • Thank you Raph - maybe I should have specified in my answer that the actual list does not necessarily contain int values. Actually the values that I use cannot be ordered (i.e. the corresponding classes do not implement `IComparable`). Thus `SequenceEqual` is not an option as it checks the order of the items. – feO2x Jan 29 '15 at 16:05
  • If you have an identity property on you class, maybe you can use it to order by this property. In the worst case, you can implement a custom IEqualityComparer for your class. – rducom Jan 29 '15 at 16:10
3

If the items in your collection are guaranteed to be unique, you could use a HashSet. That's because a HashSet is unordered by nature.

[Fact]
public void Foo()
{
    var expected = new HashSet<int> { 1, 2 ,3 };
    var actual = new HashSet<int> { 3, 2, 1 };

    Assert.Equal(expected, actual);
}

This works because xUnit uses the ISet.SetEquals() method.

This method ignores the order of elements and any duplicate elements in other.

If the actual collection is just a regular collection (not a HashSet) then you can still use SetEquals() yourself but you must realize that duplicates will be ignored.

[Fact]
public void Foo()
{
    var expected = new HashSet<int> { 1, 2 ,3 };
    var actual = new [] { 3, 2, 1, 1, 1 };

    // This also passes, but may not be what you want
    Assert.True(expected.SetEquals(actual));
}
Steven Liekens
  • 13,266
  • 8
  • 59
  • 85
  • 1
    Thanks for your solution. The fact that it only works properly for collections with unique items might be a bummer, especially as checking the count is not enough - e.g. when comparing `{ 1, 1, 2, 3 }` with `{1, 2, 3, 3 }`: both have the same count, but another item is duplicated. – feO2x Jul 13 '21 at 07:54
  • Thanks, rolled back the `Count` suggestion since it's wrong – Steven Liekens Jul 13 '21 at 18:07
0

You can use CollectionAssert.AreEquivalent from Microsoft

CollectionAssert.AreEquivalent(expected, actual);
mantal
  • 1,093
  • 1
  • 20
  • 38
0

This is almost the same as your code. The only simplification is using Assert.Contains instead of Assert.True(expected.Contains(...)).

[Fact]
public void SomeTest()
{
    // Do something in Arrange and Act phase to obtain a collection
    List<int> actual = ...

    // Now the important stuff in the Assert phase
    var expected = new List<int> { 42, 87, 30 };
    Assert.Equal(expected.Count, actual.Count);
    foreach (var item in expected)
        Assert.Contains(item, actual);
}
Alex Jorgenson
  • 891
  • 1
  • 13
  • 17