Create mock and assign it to SetupResult or Expect from a MethodInfo

7 views
Skip to first unread message

Vladan

unread,
Sep 22, 2008, 4:39:30 AM9/22/08
to Rhino Tools Dev
I have a bit of a strange situation.

I have an Repository implementation which is based on a fluent
interface for defining a Find query, it looks something like this:

var returnedItems = repository.Find(some_ICriteria)
.Order(some_Order)
.Group(some_Projection_for_group)
.All()


Now, when testing this I need to write for example for SetupResult the
following tedious code:

var findMock = mocks.DynamicMock<FindQueryPart>();
var orderMock = mocks.DynamicMock<OrderQueryPart>();
var groupMock = mocks.DynamicMock<GroupQueryPart>();
var returnedItems = new List();

SetupResult.For(repository.Find(some_ICriteria)).Return(findMock);
SetupResult.For(findMock.Order(some_Order)).Return(orderMock);
SetupResult.For(orderMock.Group(some_Projection_for_group)).Return(groupMock);
SetupResult.For(groupMock.All()).Return(returnedItems);


As you can imagine, for a single query call this is alot of code, and
somehow I feel it smells (I need this amount of code because each step
in the fluent interface above returns a different object via interface
(e.g. IFindQueryPart).

One important thing is that the SetupResult must chain the methods in
exactly the way they've been chained in the call (for example Order
must be chained to Group and not vice versa).



Now to aid this, I've written a Lambda expression inspector (simple
stuff) which call looks like this:

MockFromLambda(repository => repository.Find(some_ICriteria)
.Order(some_Order)
.Group(some_Projection_for_group)
.All());


And it is able to return to me a MethodInfo object for each call in
the chain (Find call, Order call, Group call and All call).

My question is now... how can I generate a DynamicMock from the
information in that MethodInfo? and after that feed that to the
SetupResult?

The idea would be to dynamically generate the chain of mocks from the
Lambda expression and chain them all together?


Any ideas anyone? Has anyone tried this before?


Thanks,
Vladan

Vladan

unread,
Sep 22, 2008, 4:50:34 AM9/22/08
to Rhino Tools Dev
Actually sorry, I've expressed something wrong...

from MethodInfo I get the type that needs to be mocked... so I can
easily create the mock itself:

var mock = mocks.DynamicMock(methodInfo.ReturnType);


... now how can I assign this?

From methodInfo I have a method to which this mock needs to be
assigned to (methodInfo.Name), if I could only do this:

SetupResult.For(findMethodInfo).Return(findMethodReturnTypeMock);

Vladan

unread,
Sep 23, 2008, 3:03:38 AM9/23/08
to Rhino Tools Dev
no one?

Simone Busoli

unread,
Sep 23, 2008, 10:58:51 AM9/23/08
to rhino-t...@googlegroups.com
Here's what I've come up with, I'm not sure what are you trying to do exactly.

[TestFixture]
public class CallChain
{
    [Test]
    public void Test()
    {
        var mockery = new MockRepository();

        var repository = mockery.DynamicMock<IRepository>();
        var findMock = mockery.DynamicMock<IFindQueryPart>();
        var orderMock = mockery.DynamicMock<IOrderQueryPart>();
        var groupMock = mockery.DynamicMock<IGroupQueryPart>();
        var returnedItemsStub = new ArrayList();

        var mocks = new object[] { groupMock, orderMock, findMock, repository };

        var methods = MethodsFromLambda(repo => repo.Find(null)
                                                 .Order(null)
                                                 .Group(null)
                                                 .All()).ToList();

        using (mockery.Record())
        {
            for (var i = methods.Count - 1; i > 0; i--)
                SetupResult.For(methods[i].Invoke(mocks[i], new object[] {null}))
                    .IgnoreArguments()
                    .Return(mocks[i - 1]);

            SetupResult.For(methods[0].Invoke(groupMock, null)).IgnoreArguments().Return(returnedItemsStub);
        }
        using (mockery.Playback())
        {
            var returnedItems = repository.Find(null)
                .Order(null)
                .Group(null)
                .All();

            Assert.That(returnedItems, Is.EqualTo(returnedItemsStub));
        }
    }

    private static IEnumerable<MethodInfo> MethodsFromLambda(Expression<Action<IRepository>> expr)
    {
        var body = expr.Body as MethodCallExpression;

        while (body != null)
        {
            yield return body.Method;
            body = Expression.Lambda<Action<IRepository>>(body.Object, expr.Parameters).Body as MethodCallExpression;
        }
    }
}

public interface IRepository
{
    IFindQueryPart Find(object o);
}

public interface IFindQueryPart
{
    IOrderQueryPart Order(object order);
}

public interface IOrderQueryPart
{
    IGroupQueryPart Group(object projection_for_group);
}

public interface IGroupQueryPart
{
    ArrayList All();

Vladan

unread,
Sep 25, 2008, 6:52:53 AM9/25/08
to Rhino Tools Dev
.Invoke ..... aaaaargh, it was right in front of me and I forgot about
it... thnx so much for the idea! it should work perfectly now.

Ill try it out tonight and let you know.




Vladan

Simone Busoli

unread,
Sep 25, 2008, 6:56:36 AM9/25/08
to rhino-t...@googlegroups.com
Good. One question, how do you generalize this approach? I mean, you call chain involves stuff you need to mock - actually, stub - and a final return type. I dealt with it leaving the last call out of the loop and handling it by hand. I'd like to see what you come up with in the end. BTW, seems a good usage of the auto mocking container.

Vladan

unread,
Sep 25, 2008, 7:31:14 AM9/25/08
to Rhino Tools Dev
My idea is to provide a chain and a result to attach at the end of the
chain and have lambda inspector go over the chain and connect the dots
(mock them).

Automocking container will not work I think because these are not
dependencies, but chaining... although IMO each of them could end in
the container and be accessible (additional benefit, but not needed
for me at this point).

My current idea is something like this:

EntityRepositoryMocker.SetupMock(repo =>
repo.Find(byCriteria).Order(byOrder).Group(byGroup).All())
.On(customerRepository.Find(byCriteria))
.Return(someObjectWhichShouldBeOfSameTypeAsTheLastMethodInChainWhichReturnsIt)


The end object of "AttachTo" is IMethodOptions, so the "Return" part
is actually Rhino part which is then flexible enough to add
constraints and such further on on the last method in chain.

Besides this, if everything goes ok, you would also have instead of
SetupMock, a SetupExpectation which would work in exact same way
except it would do instead of SetupResult.For, an Expect.Call.

Ill start cracking at it now, keep your fingers crossed that it
works :)



Btw. For the generalise approach... if I have a Lambda Inspector and a
Mocker which mocks and chains the result of Inspector, basically the
solution is already generalised for any kind of fluent interface
mocking.



Vladan

Simone Busoli

unread,
Sep 25, 2008, 7:48:02 AM9/25/08
to rhino-t...@googlegroups.com
This is what I've come up with, it was an interesting experiment:

public static class MockRepositoryExtensions
    {
        public static void StubAndReturn<T, V>(this MockRepository mockery, T input, V toReturn,
                                               Expression<Func<T, V>> expr)
        {
            var methods = GetMethods(expr).Reverse().ToArray();

            var mocks = new Dictionary<Type, object>{{typeof (T), input}};

            foreach (var method in methods.Take(methods.Count() - 1))
                if (!mocks.ContainsKey(method.ReturnType))
                    mocks.Add(method.ReturnType, mockery.Stub(method.ReturnType));

            for (var i = 0; i < methods.Length; i++)
            {
                var methodArguments = methods[i].GetParameters().Select(p => p.DefaultValue);

                SetupResult.For(methods[i].Invoke(mocks[methods[i].DeclaringType],
                                                  methodArguments.Any() ? new object[] {methodArguments} : null)).
                    IgnoreArguments()
                    .Return(i < methods.Length - 1 ? mocks[methods[i].ReturnType] : toReturn);
            }
        }

        private static IEnumerable<MethodInfo> GetMethods<V, T>(Expression<Func<T, V>> expr)
        {
            var body = expr.Body as MethodCallExpression;

            while (body != null)
            {
                yield return body.Method;
                body = Expression.Lambda<Action<IRepository>>(body.Object, expr.Parameters).Body as MethodCallExpression;
            }
        }
    }

It allows me do do this:

[TestFixture]
public class CallChain
{
    [Test]
    public void Test()
    {
        var mockery = new MockRepository();

        var repository = mockery.Stub<IRepository>();
        var toReturn = new ArrayList();

        using (mockery.Record())
        {
            mockery.StubAndReturn(repository, toReturn, repo => repo.Find(null)
                                                                    .Order(null)
                                                                    .Group(null)
                                                                    .All());
        }
        using (mockery.Playback())
        {
            var returnedItems = repository.Find(null)
                .Order(null)
                .Group(null)
                .All();

            Assert.That(returnedItems, Is.EqualTo(toReturn));
        }
    }
}

Vladan

unread,
Sep 25, 2008, 8:48:53 AM9/25/08
to Rhino Tools Dev
Exactly the same idea! :D

My implementation was coming down functionally exactly the same, only
I didn't make as extension method and didnt use Linq Any, Select and
other methods... which now that I think about it is quite nice :D


Thanks for sharing the code!


Vladan
> ...
>
> read more »

Simone Busoli

unread,
Sep 25, 2008, 9:04:56 AM9/25/08
to rhino-t...@googlegroups.com
You're welcome. Actually I didn't take the time to look at your implementation, but it looks like the final result is the same. Thanks for the neat idea, I never thought about it, it would come really useful in some situations.
Reply all
Reply to author
Forward
0 new messages