Search code examples
c#asp.netequalityviewbagxunit.net

Assert if two different objects have same values in a dynamic collection


I've run into a rather odd problem with xUnit.net when comparing two objects in a dynamic collection (the ViewBag).

I have an ActionFilter with the following method:

public void OnActionExecuting(ActionExecutingContext filterContext)
{
    var selectList = new List<SelectListItem>();

    var foos = _repo.Get();
    foreach (var foo in foos)
    {
        var selectItem = new SelectListItem()
        {
            Text = foo.Text,
            Value = foo.Value
        };
        selectList.Add(selectItem);
    }
    filterContext.Controller.ViewBag.SelectList = selectList;
}

Note how the values are wrapped in a List<SelectListItem> and then assigned to the ViewBag.

Then I have a test which tests if the values from my repository are added to the ViewBag:

public void MyTest()
{
    // Arrange
    var fakeController = Substitute.For<Controller>();
    var fakeContext = Substitute.For<ActionExecutingContext>();
    fakeContext.Controller = fakeController;

    var repository = Substitute.For<IRepository<Foo>>();
    var foo = new Foo() {Text = "Foo", Value = "Bar"};
    var foos = new List<Foo> { foo };
    repository.Get().Returns(foos);
    
    var filter = new MyFilter(repository);

    // Act
    filter.OnActionExecuting(fakeContext);
    
    // Assert
    var expected = new List<SelectListItem> {new SelectListItem {Text = foo.Text, Value = foo.Value}};
    Assert.Equal(expected, fakeContext.Controller.ViewBag.SelectList); // fails
}

This test fails with

Result Message:
Assert.Equal() Failure

Expected: List [SelectListItem { Disabled = False, Group = null, Selected = False, Text = "Foo", Value = "Bar" }]

Actual: List [SelectListItem { Disabled = False, Group = null, Selected = False, Text = "Foo", Value = "Bar" }]

To me that looks equal.

Just in case I tested if it was unexpectedly checking for if it was the same instance. But the below passes. So that's not the case.

var a = new {a = "a"};
var b = new {a = "a"};
Assert.Equal(a, b); // pass

Solution

  • Assert.Equal will call the object's "Equals" method. For List, that is simply inherited from Object, which for reference types tests for instance equality (i.e.. the same instance).

    Instead, try Enumerable.SequenceEqual (see here )

    UPDATED to include implementation of EqualityComparer:

        // Custom comparer for the SelectListItem class
        class SelectListItemComparer : IEqualityComparer<SelectListItem>
        {
            // Products are equal if their names and product numbers are equal.
            public bool Equals(SelectListItem x, SelectListItem y)
            {
    
                //Check whether the compared objects reference the same data.
                if (Object.ReferenceEquals(x, y)) return true;
    
                //Check whether any of the compared objects is null.
                if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
                    return false;
    
                //Check whether the products' properties are equal.
                return x.Text.Equals(y.Text) && x.Value.Equals(y.Value);
            }
    
            // If Equals() returns true for a pair of objects 
            // then GetHashCode() must return the same value for these objects.
    
            public int GetHashCode(SelectListItem item)
            {
                //Check whether the object is null
                if (Object.ReferenceEquals(item, null)) return 0;
    
                //Get hash code for the Name field if it is not null.
                int hashText = item.Text == null ? 0 : item.Text.GetHashCode();
    
                //Get hash code for the Code field.
                int hashValue = item.Value.GetHashCode();
    
                //Calculate the hash code for the product.
                return hashText ^ hashValue;
            }
    
        }
    

    then we can do:

            // Assert
            var expected = new List<SelectListItem> {new SelectListItem {Text = "this", Value = "that"}};
            var actual = new List<SelectListItem> {new SelectListItem {Text = "this", Value = "that"}};
            Assert.IsTrue(expected.SequenceEqual(actual, new SelectListItemComparer()));