2

I'm trying to create a Unit Test that compares two lists of string arrays.

I tried creating two of the exact same List<string[]> objects, but when I use CollectionAssert.AreEqual(expected, actual);, the test fails:

[TestMethod]
public void TestList()
{
    List<string[]> expected = new List<string[]> {
        new string[] { "John", "Smith", "200" },
        new string[] { "John", "Doe", "-100" }
    };

    List<string[]> actual = new List<string[]> {
        new string[] { "John", "Smith", "200" },
        new string[] { "John", "Doe", "-100" }
    };

    CollectionAssert.AreEqual(expected, actual);
}

I've also tried Assert.IsTrue(expected.SequenceEqual(actual));, but that fails as well.

Both these methods work if I am comparing two Lists of strings or two arrays of strings, but they do not work when comparing two Lists of arrays of strings.

I'm assuming these methods are failing because they are comparing two Lists of object references instead of the array string values.

How can I compare the two List<string[]> objects and tell if they are really the same?

10
  • 2
    Try this: expected.Zip(actual, (e, a) => e.SequenceEqual(a)).All(x => x). Commented Sep 17, 2017 at 22:48
  • Out of curiosity... would you consider the lists "equal" if they have the same elements but in a different order? Also, it's worth noting that a string array is a terrible substitute for an object. Commented Sep 17, 2017 at 22:53
  • @David For this specific test, I'd be okay with a solution that requires the elements to be in the same order, as well as a solution that ignores the order. And I agree that objects are usually better than string arrays. This code is part of a bigger picture and needs to be in this format. Commented Sep 17, 2017 at 23:06
  • 1
    @JeppeStigNielsen - Excellent point re .Zip not comparing the full lengths. Commented Sep 18, 2017 at 0:18
  • 1
    .Contains(false) negated is not more (nor less) efficient than .All(x => x). Both "consume" the source until they find an entry which is false. The first compares each element to false with the default equality comparer for bool. The second invokes the delegate predicate which wraps the static IL method that comes out of the lambda arrow x => x, and checks the return values. Since the runtime will do inlining in either case, I think both will be equally fast (I did not measure). Agree with @Enigmativity. Commented Sep 18, 2017 at 7:34

3 Answers 3

5

It is failing because the items in your list are objects (string[]) and since you did not specify how CollectionAssert.AreEqual should compare the elements in the two sequences it is falling back to the default behavior which is to compare references. If you were to change your lists to the following, for example, you would find that the test passes because now both lists are referencing the same arrays:

var first = new string[] { "John", "Smith", "200" };
var second = new string[] { "John", "Smith", "200" };

List<string[]> expected = new List<string[]> { first, second};
List<string[]> actual = new List<string[]> { first, second};

To avoid referential comparisons you need to tell CollectionAssert.AreEqual how to compare the elements, you can do that by passing in an IComparer when you call it:

CollectionAssert.AreEqual(expected, actual, StructuralComparisons.StructuralComparer);
Sign up to request clarification or add additional context in comments.

Comments

5

CollectionAssert.AreEqual(expected, actual); fails, because it compares object references. expected and actual refer to different objects.

Assert.IsTrue(expected.SequenceEqual(actual)); fails for the same reason. This time the contents of expected and actual are compared, but the elements are themselves different array references.

Maybe try to flatten both sequences using SelectMany:

var expectedSequence = expected.SelectMany(x => x).ToList();
var actualSequence = actual.SelectMany(x => x).ToList();
CollectionAssert.AreEqual(expectedSequence, actualSequence);

As Enigmativity correctly noticed in his comment, SelectMany may give a positive result when the number of arrays and/or their elements are different, but flattening the lists will result in an equal number of elements. It is safe only in the case when you always have the same number of arrays and elements in these arrays.

2 Comments

You've nailed it, with the explanation of the object references, but the .SelectMany approach creates a whole bunch of "false positives" and there's no need for .Select(y => y)
Thanks for the comment. I missed that. I edited my post.
0

The best solution would be to check both the items in each sub-collection as well as the number of items in each respective sub-collection.

Try with this:

bool equals = expected.Count == actual.Count &&
              Enumerable.Range(0, expected.Count).All(i => expected[i].Length == actual[i].Length &&
                                                           expected[i].SequenceEqual(actual[i]));
Assert.IsTrue(equals);

This will check that:

  • Both the lists have the same length
  • All the pair of sub-collections in both lists have the same length
  • The items in each pair of sub-collections are the same

Note: using SelectMany isn't a good idea since it could create a false positive you have the same items in your second list, but spread out in different sub-collections. I mean, it'd consider two lists to be the same even if the second one had the same items all in a single sub-collection.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.