Partial matching: 'ShouldLookLike' and 'LooksLike'

ShouldLookLike() compares two complex objects, but what if you only want to compare just some of the properties of an object, instead of all?

Suppose you have an Account model but the value of the AccountNumber property is a random integer and its assignment is beyond your control. You cannot assert its value because the correct value isn't readily known. A spec like this will fail:

[Test]
public void then_returns_the_expected_model()
{
     _account.ShouldLookLike(new Customer
     {
       		//AccountNumber = ???
          CustomerName = "Atticus Finch",
          Balance = 5042.61
     });
}

The _account object has a value for its AccountNumber property, but the Customer that it is being compared to does not have that property initialized, so it will have its default value. That means the two objects will not look the same, as they will differ by their AccountNumber properties.

There's an overload of ShouldLookLike that accepts a lambda expression as input. When you pass in a lambda expression that creates a new instance of the object you wish to compare to, SpecsFor will only validate the properties that you initialize. All other properties are ignored. So, you can ignore the AccountNumber property like so:

[Test]
public void then_returns_the_expected_model()
{
     _account.ShouldLookLike(() => new Customer
     {
       		//AccountNumber = ? //Ignore!
          CustomerName = "Atticus Finch",
          Balance = 5042.61
     });
}

As long as the _account object's CustomerName and Balance properties match, the spec will pass. All other properties are ignored.

There are some limitations to the types of expressions that you can use with this overload of ShouldLookLike. To get around those limitations, SpecsFor also provides the ShouldLookLikePartial method.

ShouldLookLikePartial

🚧

Be careful!

Whenever possible, use the strongly-typed partial matching that's described above. Anonymous objects are not refactor-friendly, and your specs may break at test time if properties are renamed or removed.

ShouldLookLikePartial accepts an anonymous object as input. You can exclude the AccountNumber property from comparison by using ShouldLookLikePartial() and not including the AccountNumber property in the anonymous object.

[Test]
public void then_returns_the_expected_model()
{
     _account.ShouldLookLikePartial(new
     {
          CustomerName = "Atticus Finch",
          Balance = 5042.61
     });
}

As long as the _account object has the expected CustomerName and Balance, this spec will pass.

πŸ“˜

Did you know?

Both ShouldLookLike and ShouldLookLikePartial can work with complex, nested objects, including arrays and collections. You are not limited to comparing objects with simple value types.

Partial Matching with Looks.Like

πŸ“˜

This functionality is only available in SpecsFor 4.3.0+

When setting up expectations or verifying a mock's behavior, you may not always care about every property on objects that are passed to your mock object's methods. Moq's It.Is() method will allow you to check specific conditions, but checking multiple properties with it quickly becomes tedious:

Instead, you can use Looks.Like to check that a parameter looks like a given object. If you only care about specific properties, you can use the overload of Looks.Like that accepts a lambda expression, like so:

public class when_verifying_with_a_partial_object : SpecsFor<object>
		{
			[Test]
			public void then_it_verifies_correctly_if_the_object_matches_the_specified_properties()
			{
				var myCar = new TrainCar {Name = "Simple Car", MaxPassengers = 100, YearBuilt = 2014};

				GetMockFor<ITrainYard>().Object
					.StoreCar(myCar);

				GetMockFor<ITrainYard>()
					.Verify(x => x.StoreCar(Looks.Like(() => new TrainCar{YearBuilt = 2014})));
			}
		}

Only the properties that you initialize in your lambda expression will be validated. Everything else will be ignored.