Matchers for complex objects

1,034 views
Skip to first unread message

Will Green

unread,
Jul 14, 2011, 9:06:58 PM7/14/11
to NSubstitute
I've been struggling with the issue of matching complex objects
(objects with multiple properties). Using a Predicate<T> with
Arg.Is<T> is nice for simple objects, but when your object has, say,
10 properties that you want to verify, and one of them causes the
criteria defined in the Predicate to fail, you don't get any
indication *which* property failed the criteria. It also seems quite
tedious to have a single assert for each property of the object.

What I'm thinking is a sort of advanced API for matching objects. The
first thing that comes to mind is adding an overload to Arg.Is that
takes a Func<T,IEnumerable<CriteriaFailure>>. In this Func, you would
check each property as appropriate, and add CriteriaFailure objects to
a list that the Func returns. If the list is not empty, NSubstitute
would throw an assertion exception that would list out each error, so
you'd know what property failed.

The CriteriaFailure object could have the Member name, the Expected
value, the Actual Value, and an optional message.

What do you think?

==
Will Green
wi...@hotgazpacho.org

David Tchepak

unread,
Jul 15, 2011, 1:20:36 AM7/15/11
to nsubs...@googlegroups.com
Completely agree NSubstitute needs to give more help with this.

Maybe pass in a builder of some sort rather than forcing people to new-up an IEnumerable<CriteriaFailure>s?

sub.Received().SomeCall(Arg.Is<Foo>((arg, result) => {
  if (arg.Something != 2) result.NonMatch("Something not 2");
  if (arg.SomethingElse == null) result.NonMatch("SomethingElse is null");  
  //...
  }));

This might be too messy to do inline, so maybe we'd want to extract custom matchers to a method. In which case would it be cleaner to put it behind an IArgMatcher interface, maybe with an abstract base class to help quickly implement matchers?



--
You received this message because you are subscribed to the Google Groups "NSubstitute" group.
To post to this group, send email to nsubs...@googlegroups.com.
To unsubscribe from this group, send email to nsubstitute...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/nsubstitute?hl=en.


Will Green

unread,
Jul 15, 2011, 7:23:04 AM7/15/11
to NSubstitute
I like the IArgMatcher and abstract base class idea! Much cleaner.

Something like:

public interface IArgMatcher
{
void Match(object arg);
bool IsMatch();
}

public abstract class ArgMatcher<T> : IArgMatcher
{
private ICollection<MatchFailure> _matchFailures = new
List<MatchFailure>();

protected abstract void Match(T arg);
protected void NonMatch(Expression<Func<T, object>>
memberExpression, object expected) { ... }
protected void NonMatch(Expression<Func<T, object>>
memberExpression, object expected, string message) { ... }

public void Match(object arg)
{
Match(arg as T);
}

public bool IsMatch()
{
if(_matchFailures.Any())
{
throw new ArgumentMismatchException(_matchFailures);
}
}
}

The NonMatch methods would take a member expression (which would aide
in refactoring, and also allow us access to the actual value of the
member) and take care of creating the MatchFailure objects and adding
them to the _matchFailures collection.

On Jul 15, 1:20 am, David Tchepak <tche...@gmail.com> wrote:
> Completely agree NSubstitute needs to give more help with this.
>
> Maybe pass in a builder of some sort rather than forcing people to new-up an
> IEnumerable<CriteriaFailure>s?
>
> sub.Received().SomeCall(Arg.Is<Foo>((arg, result) => {
>   if (arg.Something != 2) result.NonMatch("Something not 2");
>   if (arg.SomethingElse == null) result.NonMatch("SomethingElse is null");
>   //...
>   }));
>
> This might be too messy to do inline, so maybe we'd want to extract custom
> matchers to a method. In which case would it be cleaner to put it behind an
> IArgMatcher interface, maybe with an abstract base class to help quickly
> implement matchers?
>
> On Fri, Jul 15, 2011 at 11:06 AM, Will Green <wdg.hotgazpa...@gmail.com>wrote:
>
>
>
>
>
>
>
> > I've been struggling with the issue of matching complex objects
> > (objects with multiple properties). Using a Predicate<T> with
> > Arg.Is<T> is nice for simple objects, but when your object has, say,
> > 10 properties that you want to verify, and one of them causes the
> > criteria defined in the Predicate to fail, you don't get any
> > indication *which* property failed the criteria. It also seems quite
> > tedious to have a single assert for each property of the object.
>
> > What I'm thinking is a sort of advanced API for matching objects. The
> > first thing that comes to mind is adding an overload to Arg.Is that
> > takes a Func<T,IEnumerable<CriteriaFailure>>. In this Func, you would
> > check each property as appropriate, and add CriteriaFailure objects to
> > a list that the Func returns. If the list is not empty, NSubstitute
> > would throw an assertion exception that would list out each error, so
> > you'd know what property failed.
>
> > The CriteriaFailure object could have the Member name, the Expected
> > value, the Actual Value, and an optional message.
>
> > What do you think?
>
> > ==
> > Will Green
> > w...@hotgazpacho.org

Will Green

unread,
Jul 15, 2011, 8:00:32 AM7/15/11
to NSubstitute
Thinking a bit more, it could be even simpler:

public interface IArgMatcher
{
    void Match(object arg);
}

public abstract class ArgMatcher<T> : IArgMatcher
{    
private ICollection<MatchFailure> _matchFailures = new
List<MatchFailure>();

    protected abstract void Evaluate(T arg);

    protected void NonMatch(Expression<Func<T,
object>>memberExpression, object expected) { ... }
    protected void NonMatch(Expression<Func<T, object>
memberExpression, object expected, string message) { ... }
    public void Match(object arg)
    {
        Evaluate(arg as T);
if (_matchFailures.Any())
throw new ArgumentMatchException(_matchFailures);

Will Green

unread,
Jul 16, 2011, 12:00:52 AM7/16/11
to NSubstitute
Spent a couple house this evening spiking on this idea. What I've come
up with, I've pushed to a branch on GitHub:
https://github.com/hotgazpacho/NSubstitute/tree/ArgMatcher

I've added an override to Arg.Is<T> that takes an instance of an
IArgumentMatcher<T>, which then wraps it in a new
ArgumentSpecification class.

It's really quite simple. Would love to get some feedback!

Thanks!

==
Will Green

David Tchepak

unread,
Jul 16, 2011, 12:24:28 AM7/16/11
to nsubs...@googlegroups.com
I really like it.

Do you think we should keep Arg.Is<T> for this? Or maybe have Arg.Matches<T>(IArgumentMatcher<T> matcher) specifically for custom matchers? (So we have Arg.Is, Arg.Any, Arg.Matches?)

I'm going to try bringing this in and redoing all the existing arg specifications as matchers (just to double check the api works for lots of different cases). I've wanted to separate out the arg specifications and arg matching for a while, as arg specifications are going to start doing more than just matching from next release.

Thanks a lot for the work!

Cheers,
David


--

Will Green

unread,
Jul 16, 2011, 9:05:23 AM7/16/11
to NSubstitute
Glad you like it!

I thought about Arg.Matches<T> after my last commit. It would seem to
make more sense.

I also observed that the existing specifications could probably be re-
written with specific IArgumentMatcher implementations.

Happy to contribute! The relative ease with which I added this is a
testament to the design of the framework :-)

On Jul 16, 12:24 am, David Tchepak <tche...@gmail.com> wrote:
> I really like it.
>
> Do you think we should keep Arg.Is<T> for this? Or maybe have
> Arg.Matches<T>(IArgumentMatcher<T> matcher) specifically for custom
> matchers? (So we have Arg.Is, Arg.Any, Arg.Matches?)
>
> I'm going to try bringing this in and redoing all the existing arg
> specifications as matchers (just to double check the api works for lots of
> different cases). I've wanted to separate out the arg specifications and arg
> matching for a while, as arg specifications are going to start doing more
> than just matching from next release.
>
> Thanks a lot for the work!
>
> Cheers,
> David
>
Reply all
Reply to author
Forward
0 new messages