3

Sometimes I have to assert that two lists have the same items. With fluent assertions this can be done like this:

class MyObject { public string MyString {get; set;} }

var o1 = new MyObject { MyString = "1    " }
list1.Add(o1);

var o2 = new MyObject { MyString = "1" }
list2.Add(o2);

list1.Should().BeEquivalentTo(list2)

But sometimes I want a specific property to be compared in a different way, like this:

list1.Should().BeEquivalentTo(list2, options => options
            .Using<string>(context => context.Subject.TrimEnd().Should().Be(context.Expectation))
            .When<string>( ??????? ));

I have tried:

it => it.SelectedMemberInfo.Name == PropertyNameHere

But SelectedMemberInfo can be null and it throws an exception when null and I dont know if I'm calling it the correct way.

Update 1: Tried, but null reference exception:

        options => options
        .Using<DateTime>(it => it.Subject.Should().BeCloseTo(DateTime.Now, TimeSpan.FromMinutes(1)))
        .When(it =>
            it != null
            && it.SelectedMemberInfo != null
            && it.SelectedMemberInfo.Name == nameof(Y.X)));

2 Answers2

2

To match the MyString property, the two most used ways are either to match by type or by path.

To match MyString by type, you use WhenTypeIs<string>.

[TestMethod]
public void MatchByType()
{
    var o1 = new MyObject { MyString = "1    " };
    var list1 = new[] { o1 };

    var o2 = new MyObject { MyString = "1" };
    var list2 = new[] { o2 };

    list1.Should().BeEquivalentTo(list2, opt => opt
        .Using<string>(ctx => ctx.Subject.TrimEnd().Should().Be(ctx.Expectation))
        .WhenTypeIs<string>());
}

For the curious WhenTypeIs<TMemberType> is simply an alias for

When(info => info.RuntimeType.IsSameOrInherits(typeof(TMemberType))

To match MyString by its name, you can use When(e => e.SelectedMemberPath).

SelectedMemberPath is defined as

the full path from the root object until the current object separated by dots.

In this case the root object is MyObject, so SelectedMemberPath will be "MyString". So this can be written as:

[TestMethod]
public void MatchByName()
{
    var o1 = new MyObject { MyString = "1    " };
    var list1 = new[] { o1 };

    var o2 = new MyObject { MyString = "1" };
    var list2 = new[] { o2 };

    list1.Should().BeEquivalentTo(list2, opt => opt
        .Using<string>(ctx => ctx.Subject.TrimEnd().Should().Be(ctx.Expectation))
        .When(e => e.SelectedMemberPath.EndsWith(nameof(MyObject.MyString))));
}

UPDATE: SelectedMemberPath was renamed to Path in v.6.0.0

xavier
  • 1,860
  • 4
  • 18
  • 46
Jonas Nyrup
  • 2,376
  • 18
  • 25
0

The problem here is that you are trying to access a member in an object that hasn't been instantiated. Because it.SelectedMemberInfo is null, there is no member Name to access. The .NET documentation on NullReferenceException has a good explanation of this.

https://learn.microsoft.com/en-us/dotnet/api/system.nullreferenceexception?view=netcore-3.1

Assuming there are no other special circumstances when SelectedMemberInfo is null, you will just want to make the expression in .When() null-safe. You can do this pretty easily with a single character (null propagation):

it => it.SelectedMemberInfo?.Name == PropertyNameHere

The way null propagation works is if the object that precedes the question mark is null, it will evaluate as a null instance of the type at the end of the statement. So in this case, if it.SelectedMemberInfo is null, this will evaluate as (string) null.

If you like extra lines, you could also do this -

it => it.SelectedMemberInfo != null && it.SelectedMemberInfo.Name == PropertyNameHere)

If there's a chance that you could have null items in the parent collection, you're also going to want a null check on that -

it => it?.SelectedMemberInfo?.Name == PropertyNameHere
Ben Alpert
  • 49
  • 2