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
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
{
}
AddIn1 is a basic implementation used for testing.
Now here is the test:
MockRepository mocks = new MockRepository();
AddInBase addIn = mocks.CreateMock<AddIn1>();
Expect.Call( delegate(IContainer container) { addIn.Load(container); } ); //doesnt compile
Expect.Call(addIn.Unload); //compile
//... (replay, test, verify)
Thx in advance.
Expect.Call( delegate { addIn.Load(container); } ); //compile
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.
http://ayende.com/wiki/(S(0qrkfenqlahpcvacozrxkkry))/Rhino+Mocks.ashx
And the rhino mocks mailing list.
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 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<Proc> Call(Proc actionToExecute)
{
[...]
}
public static IMethodOptions<Proc<A1>> Call<A1>(Proc<A1> 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
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?
You're right, I missed that. Not easier to read than the delegate keyword.
Fabian
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
Fabian,
The C# compiler isn't that smart about inferencing
Damn, you are right, the C# compiler cannot infer the generic arguments from what I wrote above! Quite lame, actually.
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<T>() which only works for non-void methods.
Do we know why this occurs and has anyone else seen this behavior?
I have not run into this behavior, can you explain a bit furhter, do you have any addons that affect intelklisense?
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.
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<IFoo>();
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<string>(foo.GetValue()).", I get the intellisense.
Does that make sense? Is that by design now?
David, that is identical behavior to what we're experiencing. It happens on all machines we've tried it on.
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<T> Call<T>(T ignored) and IMethodOptions<Action> Call(Action actionToExecute).
Jeff,
sorry for taking so long to respond, I have not been able to reproduce this.
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?
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.
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.
Comment preview