Capture parameter passed to mock object call

2,114 views
Skip to first unread message

alex2k8

unread,
Sep 3, 2009, 7:28:20 PM9/3/09
to Moq Discussions
Hello,

Consider I would like to capture parameter passed to mock object call
to
examine it's properties latter. Currently we can do this using
Callbacks

- - - - - - - - - -
Job job = null;
var service = new Mock<IService>();
service.Setup(s => s.Create(It.IsAny<Job>())).Callback((Job j) => job
= j);

service.Object.Create(new Job { Title = "title", Description =
"description" });

AssertAreEqual("title", job.Title);
AssertAreEqual("description", job.Description);
- - - - - - - - - -

I have an alternative solution:
- - - - - - - - - -
var service = new Mock<IService>();
var job = Mock.Capture<Job>();
service.Setup(x => x.Create(job.Object));

service.Object.Create(new Job { Title = "title", Description =
"description" });

AssertAreEqual("title", job.Object.Title);
AssertAreEqual("description", job.Object.Description);
- - - - - - - - - -

This is easy to implement (and actually done already). What do you
think
about this approach? I am new to Moq, may be this is implemented
already or in plans?

- Alex

Daniel Cazzulino

unread,
Sep 4, 2009, 8:48:23 PM9/4/09
to moq...@googlegroups.com
I'm a bit confused.
So you setup the call so that it matches your ("captured"?) mock object, and later you call the method with another new instance which is not the captured one, and you somehow made it behave as an It.IsAny<> matcher and copy all properties when the call happens?

I think it might be easier to understand if you had an extension method on Mock<T> that exposed that behavior in an encapsulated manner:

Job job = null;
service.Capture<Job>(x => x.Create(It.IsAny<Job>()), job);

service.Object.Create(....);

Assert.NotNull(job);
.... 

you'd have multiple overloads of the extension method.

This sounds useful. What do others think?

/kzu

--
Daniel Cazzulino | Developer Lead | XML MVP | Clarius Consulting | +1 425.329.3471

alex2k8

unread,
Sep 6, 2009, 7:51:55 AM9/6/09
to Moq Discussions
Helo, Daniel

> service.Capture<Job>(x => x.Create(It.IsAny<Job>()), job);

I think this intended to be

service.Capture<Job>(x => x.Create(It.IsAny<Job>()), ref job);

but I doubt we can implement this, as "lambda expression cannot
directly capture a ref or out parameter from an enclosing method.". If
we could, than the ideal syntax probably would be:

service.Setup(x => x.Create(It.IsAny<Job>(ref job)));

- Alex

alex2k8

unread,
Sep 6, 2009, 8:48:37 AM9/6/09
to Moq Discussions
> but I doubt we can implement this

Seem I was wrong. After some thinking I implemented a proof of concept
for this use case:

Daniel Cazzulino

unread,
Sep 6, 2009, 12:46:29 PM9/6/09
to moq...@googlegroups.com
why would the ref be part of a lambda? It could be just a param to Capture....
--

alex2k8

unread,
Sep 6, 2009, 12:53:51 PM9/6/09
to Moq Discussions
We need to have some kind of reference to 'job' variable to set it's
value. I think if we simply pass 'job' than we can't set it's value.
Thought may be you know a way?

alex2k8

unread,
Sep 6, 2009, 1:19:26 PM9/6/09
to Moq Discussions
Btw, this is how it looks now:

Job job = null;
service.Setup(x => x.Create(It.IsAny(ref job)));
service.Object.Create(new Job { Title = "title", Description =
"description" });

AssertAreEqual("title", job.Title);
AssertAreEqual("description", job.Description);

- Alex

Daniel Cazzulino

unread,
Sep 6, 2009, 5:04:49 PM9/6/09
to moq...@googlegroups.com
that looks great!

would you mind sharing a patch so I can take a look?


/kzu

--
Daniel Cazzulino | Developer Lead | XML MVP | Clarius Consulting | +1 425.329.3471


alex2k8

unread,
Sep 7, 2009, 8:05:38 AM9/7/09
to Moq Discussions
I don't know how to make it better, so will send my proof of concept
changes right here (let me know if I can send files some how).

1) Matcher.cs

internal partial class Matcher : IMatcher
{
//new:
public Match GetMatch()
{
return match;
}

...
}

2) Match.cs

public abstract class Match
{
//new:
internal Expression ArgumentExpression { get; set; }

...
}

3) It.cs

public static class It
{
//new:
public static TValue IsAny<TValue>(ref TValue capture)
{
if (TlsContext.IsCurrentArgumentSet)
{
capture = (TValue)TlsContext.GetCurrentArgument();
return default(TValue);
}

return Match<TValue>.Create(value =>
{
return value == null || typeof(TValue).IsAssignableFrom
(value.GetType());
}, () => It.IsAny<TValue>());
}

...
}

4) MatcherFactory.cs

if (expression.NodeType == ExpressionType.Call)
{
var call = (MethodCallExpression)expression;

// Try to determine if invocation is to a matcher.
using (var context = new FluentMockContext())
{
Expression.Lambda<Action>(call).Compile().Invoke();

//old:
//if (context.LastMatch != null)
// return new Matcher(context.LastMatch);

//new:
if (context.LastMatch != null)
{
Matcher matcher = new Matcher(context.LastMatch);
matcher.GetMatch().ArgumentExpression = call;
return matcher;
}
}

5) MethodCall.cs

public virtual void Execute(ICallContext call)
{
Invoked = true;

if (callback != null)
callback(call.Arguments);

if (exception != null)
throw exception;

//new:
for (int it = 0; it < argumentMatchers.Count; it++)
{
var argumentMatcher = argumentMatchers[it];

if (argumentMatcher as Matcher != null)
{
Match match = ((Matcher)argumentMatcher).GetMatch();
TlsContext.SetCurrentArgument(call.Arguments[it]);
Expression.Lambda<Action>(match.ArgumentExpression).Compile
().Invoke();
TlsContext.ClearCurrentArgument();
}
}

6) TlsContext.cs

//new:
internal class TlsContext
{
[ThreadStatic]
private static object _currentArgument;

[ThreadStatic]
private static bool _isCurrentArgumentSet;

internal static void SetCurrentArgument(object value)
{
_currentArgument = value;
_isCurrentArgumentSet = true;
}

internal static object GetCurrentArgument()
{
if (!_isCurrentArgumentSet)
throw new Exception("CurrentArgument is not set.");

return _currentArgument;
}

internal static void ClearCurrentArgument()
{
_isCurrentArgumentSet = false;
}

public static bool IsCurrentArgumentSet
{
get { return _isCurrentArgumentSet; }
}
}


- Alex

alex2k8

unread,
Sep 13, 2009, 4:15:18 PM9/13/09
to Moq Discussions
Hello, All

I experimented with Verify method as well, this is how it works:

- - - - -
Job job = null;

service.Object.Create(new Job { Title = "title 1", Description =
"description 1" });
service.Object.Create(new Job { Title = "title 2", Description =
"description 2" });

service.Verify(x => x.Create(It.IsAny(ref job)));
AssertAreEqual("title 1", job.Title);
AssertAreEqual("description 1", job.Description);

service.Verify(x => x.Create(It.IsAny(ref job)));
AssertAreEqual("title 2", job.Title);
AssertAreEqual("description 2", job.Description);
- - - - -

One thing to notice is that Verify captures calls in order execution.
This differs from normal Verify semantic, where we have no notion of
order.

- Alex

kzu

unread,
Nov 10, 2009, 7:16:01 AM11/10/09
to Moq Discussions
I don't understand how you can use a "ref job" inside an It.IsAny
which is an expression, since the compiler shouldn't let you :S
Reply all
Reply to author
Forward
0 new messages