Simplifying Rhino.Mocks; Round II

Just a small idea before I go to work. As follow up to my yesterdays post about Rhino.Mocks. I figured, that instead of supplying arrays of object for constructor parameters, which is error prone and doesn’t give you the safety of compiler checks, we can actually insert the call to constructor using Expressions (notice that this is solution that will work only in .NET >= 3.5).

Here’s quick and dirty sketch of remade method:

public TTypeToMock Mock<TTypeToMock>(Kind mockKind, Expression<Func<TTypeToMock>> ctorCall, params Type[] extraTypes)
{
    if (extraTypes == null) throw new ArgumentNullException("extraTypes");
    if (ctorCall == null) throw new ArgumentNullException("ctorCall");
 
    if (mockKind == Kind.WithRemoting)
        mockKind &= Kind.Strict;
    else if (!_mockMethods.ContainsKey(mockKind))
    {
        if ((mockKind & Kind.WithRemoting) != 0)
            throw new ArgumentException("This kind of mock does not support remoting.");
        throw new ArgumentException("Invalid mock kind.", "mockKind");
    }
    var newCall = ctorCall.Body as NewExpression;
    if (newCall == null)
        throw new ArgumentException("Not a constructor call.", "ctorCall");
    var ctorArgs = new object[newCall.Arguments.Count];
    for (int i = 0; i < ctorArgs.Length; i++)
    {
        var param = newCall.Arguments[i] as ConstantExpression;
        if (param == null)
            throw new ArgumentException("You may only place constant values in calls to the constructor.");
        ctorArgs[i] = param.Value;
    }
    return CreateMock<TTypeToMock>(mockKind, extraTypes, ctorArgs);
}
 
private TTypeToMock CreateMock<TTypeToMock>(Kind mockKind, Type[] extraTypes, object[] ctorArgs)
{
    //NOTE: possibly with lock in this call
    var mock = (TTypeToMock) _mockMethods[mockKind](_repo, typeof (TTypeToMock), ctorArgs, extraTypes);
    return mock;
}

And with that you get in return this:

WindowClipping

Notice that you no longer need to specify <MyClass> (it’s grayed out) since compiler can infer that from the Expression.

Technorati Tags: ,

Comments

It’s a nice approach.
You still need to provide the loosely typed object[] args for ctors that are protected.

Hi Krzysztof,
I was giving this idea a shot for Moq, but ended up with the following conclusion (see http://code.google.com/p/moq/issues/detail?id=44):

I figured out that no matter how cool this seems to be, the applicability is rather thin. Think about the kinds of things you mock and how many of those can be “new’ed”: interfaces and abstract classes make the majority in my case, and I can’t “new” any of those, so having a strong-typed ctor for those in the form of a Func will not work.

So we’ll not implement this for now.

Daniel,
My idea for that was to be a strongly-typed, IntelliSense driven replacement for similar thing in Rhino Mocks, that is used for concrete classes (or abstract ones – by the way, did you succeed with extending that for abstract classes?).
As for its general usability you’re absolutely right.

Thomas says:

Hi, Whats about two or more parameter constructors ?

What about it?

It is very straightforward, and actually this implementation should support it.

Thomas says:

I mean:

If I have such a code:

MockRepository mocks = new MockRepository();
var stubArgs = mocks.Stub<ChangeBoolValueEventArgs>(new object[] { true, false });

How can I use lambda expression if Stub<> takes object[] or System.Action with a single parameter ?

Thanks

mock.Stub<ChangeBoolValueEventArgs>(()=>new ChangeBoolValueEventArgs(true, false));

Thomas says:

thanks but I’ve already tried it and it doesn’t compile. I’m using Rhino Mocks 3.5

that’s because the code i provided was a custom extension provided as part of discussion on potential directions the RM could take in the future.

It didn’t get rolled into the framework. Probably mostly due to not much interest and the reasons Daniel outlined in the above comment.

If you want to see this in the RM, Ayende is collecting ideas for RM v4.0 you can add this as feature request.

Thomas says:

Ah ok, I’m sorry I didn’t follow the diuscussion from the begining.