简体   繁体   中英

How to assert that two list contains elements with the same public properties in NUnit?

I want to assert that the elements of two list contains values that I expected, something like:

var foundCollection = fooManager.LoadFoo();
var expectedCollection = new List<Foo>() 
{
    new Foo() { Bar = "a", Bar2 = "b" },
    new Foo() { Bar = "c", Bar2 = "d" }
};

//assert: I use AreEquivalent since the order does not matter
CollectionAssert.AreEquivalent(expectedCollection, foundCollection);

However the above code will not work (I guess because .Equals() does not return true for different objects with the same value). In my test, I only care about the public property values, not whether the objects are equal. What can I do to make my assertion?

REWORKED ANSWER

There is a CollectionAssert.AreEqual(IEnumerable, IEnumerable, IComparer) overload to assert that two collections contain the same objects in the same order, using an IComparer implementation to check the object equivalence.

In the scenario described above, the order is not important. However, to sufficiently handle also the situation where there are multiple equivalent objects in the two collections, it becomes necessary to first order the objects in each collection and use one-by-one comparison to ensure that also the number of equivalent objects are the same in the two collections.

Enumerable.OrderBy provides an overload that takes an IComparer<T> argument. To ensure that the two collections are sorted in the same order, it is more or less required that the types of the identifying properties implement IComparable . Here is an example of a comparer class that implements both the IComparer and IComparer<Foo> interfaces, and where it is assumed that Bar takes precedence when ordering:

public class FooComparer : IComparer, IComparer<Foo>
{
    public int Compare(object x, object y)
    {
        var lhs = x as Foo;
        var rhs = y as Foo;
        if (lhs == null || rhs == null) throw new InvalidOperationException();
        return Compare(lhs, rhs);
    }

    public int Compare(Foo x, Foo y)
    {
        int temp;
        return (temp = x.Bar.CompareTo(y.Bar)) != 0 ? temp : x.Bar2.CompareTo(y.Bar2);
    }
}

To assert that the objects in the two collections are the same and comes in equal numbers (but not necessarily in the same order to begin with), the following lines should do the trick:

var comparer = new FooComparer();
CollectionAssert.AreEqual(
    expectedCollection.OrderBy(foo => foo, comparer), 
    foundCollection.OrderBy(foo => foo, comparer), comparer);    

No, NUnit has no such mechanism as of current state. You'll have to roll your own assertion logic. Either as separate method, or utilizing Has.All.Matches :

Assert.That(found, Has.All.Matches<Foo>(f => IsInExpected(f, expected)));

private bool IsInExpected(Foo item, IEnumerable<Foo> expected)
{
    var matchedItem = expected.FirstOrDefault(f => 
        f.Bar1 == item.Bar1 &&
        f.Bar2 == item.Bar2 &&
        f.Bar3 == item.Bar3
    );

    return matchedItem != null;
}

This of course assumes you know all relevant properties upfront (otherwise, IsInExpected will have to resort to reflection) and that element order is not relevant.

(And your assumption was correct, NUnit's collection asserts use default comparers for types, which in most cases of user defined ones will be object's ReferenceEquals )

Using Has.All.Matches() works very well for comparing a found collection to the expected collection. However, it is not necessary to define the predicate used by Has.All.Matches() as a separate function. For relatively simple comparisons, the predicate can be included as part of the lambda expression like this.

Assert.That(found, Has.All.Matches<Foo>(f => 
    expected.Any(e =>
        f.Bar1 == e.Bar1 &&
        f.Bar2 == e.Bar2 &&
        f.Bar3= = e.Bar3)));

Now, while this assertion will ensure that every entry in the found collection also exists in the expected collection, it does not prove the reverse, namely that every entry in the expected collection is contained in the found collection. So, when it is important to know that found and expected contain are semantically equivalent (ie, they contain the same semantically equivalent entries), we must add an additional assertion.

The simplest choice is to add the following.

Assert.AreEqual(found.Count() == expected.Count());

For those who prefer a bigger hammer, the following assertion could be used instead.

Assert.That(expected, Has.All.Matches<Foo>(e => 
    found.Any(f =>
        e.Bar1 == f.Bar1 &&
        e.Bar2 == f.Bar2 &&
        e.Bar3= = f.Bar3)));

By using the first assertion above in conjunction with either the second (preferred) or third assertion, we have now proven that the two collections are semantically the same.

你有没有尝试过这样的事情?

Assert.That(expectedCollection, Is.EquivalentTo(foundCollection))

I had a similar problem. Listing contributors, which contains "commenters" and other ppl... I want to get all the comments and from that derive the creators, but I'm ofc only interested in unique creators. If someone created 50 comments I only want her name to appear once. So I write a test to see that the commenters are int the GetContributors() result.

I may be wrong, but what I think your after (what I was after when I found this post) is to assert that there are exactly one of each item in one collection, found in another collection.

I solved this like so:

Assert.IsTrue(commenters.All(c => actual.Count(p => p.Id == c.Id) == 1));

If you also want the resulting list not to contain other items than expected you could just compare the length of the lists as well..

Assert.IsTrue(commenters.length == actual.Count());

I hope this is helpful, if so, I'd be very grateful if you would rate my answer.

To perform equivilance operations on complex types you need to implement IComaprable.

http://support.microsoft.com/kb/320727

Alternatively you could use recursive reflection, which is less desirable.

One option is to write custom constraints to compare the items. Here's a nice article on the subject: http://www.davidarno.org/2012/07/25/improving-nunit-custom-constraints-with-syntax-helpers/

I recommend against using reflection or anything complex, it just adds more work/maintenace.

Serialize the object (i recommend json) and string compare them. I'm unsure why you object to order by but I'd still recommend it as it will save a custom compare's for each type.

And it automatically works with domain objects change.

Example (SharpTestsEx for fluent)

using Newtonsoft.Json;
using SharpTestsEx;

JsonConvert.SerializeObject(actual).Should().Be.EqualTo(JsonConvert.SerializeObject(expected));

You can write it as a simple extensions and make it more readable.

   public static class CollectionAssertExtensions
    {
        public static void CollectionAreEqual<T>(this IEnumerable<T> actual, IEnumerable<T> expected)
        {
            JsonConvert.SerializeObject(actual).Should().Be.EqualTo(JsonConvert.SerializeObject(expected));
        }
    }

and then using your example call it like so:

var foundCollection = fooManager.LoadFoo();
var expectedCollection = new List<Foo>() 
{
    new Foo() { Bar = "a", Bar2 = "b" },
    new Foo() { Bar = "c", Bar2 = "d" }
};


foundCollection.CollectionAreEqual(foundCollection);

You'll get an assert message like so:

...:"a","Bar2":"b"},{"Bar":"d","Bar2":"d"}]

...:"a","Bar2":"b"},{"Bar":"c","Bar2":"d"}]

... _ __ _ __ _ __ _ __ _ __ _ __ ^ _ __ _ _

Simple code explaining how to use the IComparer

using System.Collections;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CollectionAssert
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            IComparer collectionComparer = new CollectionComparer();
            var expected = new List<SomeModel>{ new SomeModel { Name = "SomeOne", Age = 40}, new SomeModel{Name="SomeOther", Age = 50}};
            var actual = new List<SomeModel> { new SomeModel { Name = "SomeOne", Age = 40 }, new SomeModel { Name = "SomeOther", Age = 50 } };
            NUnit.Framework.CollectionAssert.AreEqual(expected, actual, collectionComparer);
        }
    }

    public class SomeModel
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class CollectionComparer : IComparer, IComparer<SomeModel>
    {
        public int Compare(SomeModel x, SomeModel y)
        {
            if(x == null || y == null) return -1;
            return x.Age == y.Age && x.Name == y.Name ? 0 : -1;
        }

        public int Compare(object x, object y)
        {
            var modelX = x as SomeModel;
            var modelY = y as SomeModel;
            return Compare(modelX, modelY);
        }
    }
}

这使用NUnitCore程序NUnitCore NUnit 的Assertion类解决了我的问题:

AssertArrayEqualsByElements(list1.ToArray(), list2.ToArray());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM