Ayende @ Rahien

It's a girl

Using Expect.Call( void method )

It looks like there is some confusion about the way Rhino Mocks 3.3 Expect.Call support for void method works.

Let us examine this support for an instance, shall we? Here is all the code for this feature:

public delegate void Action();

public static IMethodOptions<Action> Call(Action actionToExecute)
{
	if (actionToExecute == null)
		throw new ArgumentNullException("actionToExecute", "The action to execute cannot be null");
	actionToExecute();
	return LastCall.GetOptions<Action>();
}

As you can see, this is simply a method that accept a no args delegate, execute it, and then return the LastCall options. It is syntactic sugar over the usual "call void method and then call LastCall). The important concept here is to realize that we are using the C# * anonymous delegates to get things done.

Let us see how it works?

[Test]
public void InitCustomerRepository_ShouldChangeToCustomerDatabase()
{
	IDbConnection mockConnection = mocks.CreateMock<IDbConnection>();
	Expect.Call(delegate { mockConnection.ChangeDatabase("myCustomer"); });
	
	mocks.ReplayAll();

	RepositoryFactory repositoryFactory = new RepositoryFactory(mockConnection);
	repositoryFactory.InitCustomerRepository("myCustomer");
	
	mocks.VerifyAll();
}

This is how we handle void methods that have parameters, but as it turn out, we can do better for void methods with no parameters:

[Test]
public void StartUnitOfWork_ShouldOpenConnectionAndTransaction()
{
	IDbConnection mockConnection = mocks.CreateMock<IDbConnection>();
	Expect.Call(mockConnection.Open); // void method call
	Expect.Call(mockConnection.BeginTransaction()); // normal Expect call
	
	mocks.ReplayAll();

	RepositoryFactory repositoryFactory = new RepositoryFactory(mockConnection);
	repositoryFactory.StartUnitOfWork("myCustomer");
	
	mocks.VerifyAll();
}

Notice the second line, we are not calling the mockConnection.Open() method, we are using C#'s ability to infer delegates, which means that the code actually looks like:

Expect.Call(new Action(mockConnection.Open));

Which will of course be automatically executed by the Call(Action) method.

I hope that this will make it easier to understand how to use this new feature.

Happy Mocking,

   ~ayende

* Sorry VB guys, this is not a VB feature, but I am not going to rewrite VBC to support this :-) You will be able to take advantage of this in VB9, though, so rejoice.

Comments

Julien Rivi&#232;re
10/28/2007 11:59 AM by
Julien Rivière

It made me feel I totally misunderstood the Expect.On method. In your example you passed a string to the method of the mocked object. If I have an argument other than basic type, and declare it in the delegate, It doesn't compile.

Where can I have documentation of Rhino Mocks?

public abstract class AddInBase

{

    public abstract void Load(IContainer container);

    public abstract void Unload();

}

AddIn1 is a basic implementation used for testing.

Now here is the test:

MockRepository mocks = new MockRepository();

AddInBase addIn = mocks.CreateMock();

Expect.Call( delegate(IContainer container) { addIn.Load(container); } ); //doesnt compile

Expect.Call(addIn.Unload); //compile

//... (replay, test, verify)

Thx in advance.

Ayende Rahien
10/28/2007 12:06 PM by
Ayende Rahien

Expect.Call( delegate { addIn.Load(container); } ); //compile

Julien Rivi&#232;re
10/28/2007 12:42 PM by
Julien Rivière

Thx for the quick replay.

I had to change things in order to make it work (ie by declaring a mocked IContainer). However, I'm not sure wether I did it like it's supposed to be or not.

Keep up the good work, Rhino made me use mocks again (NMock implied too much work), even if I feel like not understand it completely.

Is there any place where I could get further information on Expect, MockRepository or RepositoryFactory usage ?

PS I'm sorry If my english is not good enough or If my questions seem stupid.

Ayende Rahien
10/28/2007 12:51 PM by
Ayende Rahien

http://ayende.com/wiki/(S(0qrkfenqlahpcvacozrxkkry))/Rhino+Mocks.ashx

And the rhino mocks mailing list.

Fabian Schmied
10/29/2007 08:46 AM by
Fabian Schmied

Hm, I'm sure you have already thought about that, but what's the reason you don't just provide overloads for different void method delegates with a different number of arguments?

Like this:

delegate void Proc();

delegate void Proc(A1 a1);

delegate void Proc<A1, A2>(A1 a1, A2 a2);

delegate void Proc<A1, A2, A3>(A1 a1, A2 a2, A3 a3);

[etc.]

public static IMethodOptions Call(Proc actionToExecute)

{

[...]

}

public static IMethodOptions<Proc> Call(Proc actionToExecute)

{

[...]

}

public static IMethodOptions<Proc<A1, A2>> Call<A1, A2>(Proc<A1, A2> actionToExecute)

{

[...]

}

[etc.]

While cumbersome to write for you, it would enable people to write:

Expect.LastCall (myObject.VoidMethod)

Expect.LastCall (myObject.VoidMethod(1))

Expect.LastCall (myObject.VoidMethod(1, 2))

and so on without needing to write anonymous delegates. Making the syntax the same for void and non-void methods.

Fabian

Ayende Rahien
10/29/2007 02:37 PM by
Ayende Rahien

Because it would have to be written as:

Expect.LastCall (myObject.VoidMethod)

Expect.LastCall (myObject.VoidMethod, 1)

Expect.LastCall (myObject.VoidMethod,1,2)

Certainly possible, but is it easy to read?

Fabian Schmied
10/29/2007 03:27 PM by
Fabian Schmied

You're right, I missed that. Not easier to read than the delegate keyword.

Fabian

Fabian Schmied
10/30/2007 07:25 AM by
Fabian Schmied

Of course, you could change the whole syntax for both void methods and non-void methods to use delegates:

Expect.Call (myObject.MyMethod).With (arg1, arg2)

(Where myObject.MyMethod is automatically converted to either a Proc<...> or a Func<...>.)

That way, the syntax would really be the same for void and non-void methods. Using overloading, the "With" method could be strongly typed (i.e. provide exactly the same signature as MyMethod), the Constraints method could be made to know how many parameter constraints to accept, and the Returns method could be made available only for non-void methods.

But at the same time, it would be a very breaking change, and you would lose that "like a method call" feeling :)

Fabian

Ayende Rahien
10/30/2007 08:04 AM by
Ayende Rahien

Fabian,

The C# compiler isn't that smart about inferencing

Fabian Schmied
10/30/2007 01:10 PM by
Fabian Schmied

Damn, you are right, the C# compiler cannot infer the generic arguments from what I wrote above! Quite lame, actually.

Jeff Litster
10/31/2007 12:03 AM by
Jeff Litster

When we use Expect.Call() with either void or non-void method calls in 3.3, we lose the intellisense we used to have.

We only get intellisense when we use:

Expect.Call() which only works for non-void methods.

Do we know why this occurs and has anyone else seen this behavior?

Ayende Rahien
10/31/2007 05:15 AM by
Ayende Rahien

I have not run into this behavior, can you explain a bit furhter, do you have any addons that affect intelklisense?

Jeff Litster
10/31/2007 04:34 PM by
Jeff Litster

We have the JetBrains Unit Test Runner plug-in. That plug-in shows signs that it may contain some of the ReSharper code in it as well (we're using the standalone test runner.) I haven't confirmed that to be true, but we'll see random JetBrains exceptions be thrown at times where intellisense events occur that ReSharper would normally handle.

That said, it was fair to assume that it might be interfering. However, I uninstalled the JetBrains Add-In and still see the same behavior when using the 3.3 Rhino.Mocks dll. When we switch back to 3.2 we get the intellisense back.

I will seek a machine that has never had the JetBrains add-in to see if we get the same behavior.

David Mohundro
11/02/2007 08:49 PM by
David Mohundro

Jeff, I'm having similar behavior if I'm understanding what you're saying correctly. Here's what it is doing for me:

Given the following:

interface IFoo

{

string GetValue();

}

void Testing()

{

MockRepository mocks = new MockRepository();

IFoo foo = mocks.CreateMock();

Expect.Call(foo.GetValue()).Return("blah");

}

When I type the "Expect.Call(foo.GetValue())." I don't get the intellisense popping up to show me "Return." If I explicitly type "Expect.Call(foo.GetValue()).", I get the intellisense.

Does that make sense? Is that by design now?

Jeff Litster
11/05/2007 05:19 PM by
Jeff Litster

David, that is identical behavior to what we're experiencing. It happens on all machines we've tried it on.

David Mohundro
11/06/2007 09:36 PM by
David Mohundro

Looking through the Expect.cs code, I'm guessing that VS Intellisense isn't smart enough to figure out that there is a difference between IMethodOptions Call(T ignored) and IMethodOptions Call(Action actionToExecute).

Ayende Rahien
11/11/2007 01:16 PM by
Ayende Rahien

Jeff,

sorry for taking so long to respond, I have not been able to reproduce this.

Peter Stromquist
11/11/2007 01:21 PM by
Peter Stromquist

Yep, getting the same behavior here now that we've upgraded to 3.3. Per the last comment, it almost seems like a bug (or feature) of the IDE. Given that, it may be a difficult one to solve.

Ayende, any updates on this?

Wojtek Kucia
11/19/2007 10:15 AM by
Wojtek Kucia

So I can also confirm the same behavior

After upgrading to Rhino 3.3

In my canse GhostDoc is on the AddOn list, but I don't think that this is the case.

Jeff Litster
11/19/2007 07:38 PM by
Jeff Litster

I spoke with Ayende about the theory that there might be a Visual Studio add-on that would be affecting the intellisense.

I asked Ayende if he had one that would affect it and he told me JetBrains ReSharper.

I downloaded and installed the evaluation version and I now have intellisense for both the Generic and non-Generic versions of Expect.Call.

ReSharper is smart enough to catch and handle the intellisense... it appears Microsoft's default intellisense is not.

While this only provides a solution of: "Get ReSharper", it at least we know how to reproduce the problem.

However, I don't know that this is a Rhino Mocks' responsibility to fix... seems like we need to be getting after Microsoft.

If Ayende feels compelled to check this out, I've confirmed that disabling the Re-Sharper plug-in will make the behavior reproducable (you don't have to uninstall it.)

If you want a quick fix for now, check out JetBrains ReSharper.

Comments have been closed on this topic.