FakeItEasy: Identify precise mismatch when a complex argument constraint fails to match an argument

From http://stackoverflow.com/a/43781563/131809

Consider the following assertion

var fake = A.Fake<ICustomerService>();

A.CallTo(() => fake.Add(
    A<Customer>.That.Matches(c =>
        c.Property == "123" &&
        c.OtherProperty == "345" &&
        c.AnotherProperty == "456" &&
        c.YetAnotherProperty == "567"
    )
)).MustHaveHappened();

This gets a bit messy.

If c.AnotherProperty is infact “890” my unit test will not tell me. It won’t tell me that AnotherProperty is wrong. It’ll just fail (correctly)

To work around this, I quite often do this:

var fake = A.Fake<ICustomerService>();

Customer addedCustomer;
A.CallTo(() => fake.Add(A<Customer>._))
    .Invokes(c => addedCustomer = c.GetArgument<Customer>(0));

Assert.IsNotNull(addedCustomer);
Assert.AreEqual("123", addedCustomer.Property);
Assert.AreEqual("345", addedCustomer.OtherProperty);
Assert.AreEqual("456", addedCustomer.AnotherProperty);
Assert.AreEqual("567", addedCustomer.YetAnotherProperty);

I guess it’d be nice if the exception MustHaveHappened throws if the matcher does not validate, somehow includes which property or properties didn’t match

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 26 (21 by maintainers)

Most upvoted comments

@alexjamesbrown, thanks for making this.

I may be missing something, but are not the calls output when MustHaveHappened fails to match? e.g.

Assertion failed for the following call:
    FakeItEasyQuestionsVS2015.MustHaveHappened+ICustomerService.Add(c: <c => ((((c.Property == "123") AndAlso (c.OtherProperty == "345")) AndAlso (c.AnotherProperty == "456")) AndAlso (c.YetAnotherProperty == "567"))>)
  Expected to find it at least once but found it #0 times among the calls:
    1: FakeItEasyQuestionsVS2015.MustHaveHappened+ICustomerService.Add(c: FakeItEasyQuestionsVS2015.MustHaveHappened+Customer)

If you can’t tell what’s wrong, have you considered augmenting the Customer rendering? Either a ToString or an ArgumentValueFormatter like

public class CustomerFormatter : ArgumentValueFormatter<Customer>
{
    protected override string GetStringValue(Customer c)
    {
        return
            $"Property: {c.Property}, OtherProperty: {c.OtherProperty}, YetAnotherProperty: {c.YetAnotherProperty}, AnotherProperty: {c.AnotherProperty}";
    }
}

yields

FakeItEasy.ExpectationException : 

  Assertion failed for the following call:
    FakeItEasyQuestionsVS2015.ICustomerService.Add(c: <c => ((((c.Property == "123") AndAlso (c.OtherProperty == "345")) AndAlso (c.AnotherProperty == "456")) AndAlso (c.YetAnotherProperty == "567"))>)
  Expected to find it at least once but found it #0 times among the calls:
    1: FakeItEasyQuestionsVS2015.ICustomerService.Add(c: Property: 123, OtherProperty: 345, YetAnotherProperty: 567, AnotherProperty: 890)

Given the complexity of implementing a solution where the particular failing clause is pinpointed, as well as the potential expansion of the error messages (as we’d have to indicate for each argument of each call which clause of the matcher failed), I propose that we close this wont-fix.

Objections?

(Matches accepts a Func<T, bool>, not an expression, so we can’t get a string representation of it).

Scratch that. There’s an extension method that takes an Expression<Func<T, bool>>, so we could do just this:

A<Customer>.That.Matches(c => c.Property == "123")
       .And(c => c.OtherProperty == "345")
       .And(c => c.AnotherProperty == "456")
       .And(c => c.YetAnotherProperty == "567")