|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Rhino Mocks Version 2.0A dynamic mock object framework for the .Net platform. It's purpose is to ease testing by allowing the developer to create mock implementations of custom objects and verify the interactions using unit testing.Rhino.Mocks is an attempt to create easier way to build and use mock objects and allow better refactoring support from the current tools. It's a hybrid approach between the pure Record/Replay of EasyMock.Net's model and NMock's expectation based model. Rhino.Mocks originated from EasyMock.Net and attempt to improve on their model to create easy to use and power mocking framework. It's free for use and modification for free and commercial software. I created Rhino.Mocks because I wanted better support from refactoring tools such as ReSharper and because I don't like the way NMock handle parameterized methods (you need to pass fake constraints to get it to recognize the correct method) and the lack of extensibility [I required hacks to get to the result that I wanted]. Licensing: Rhino Mocks is Free Software which is released under the BSD license. You can get Rhino Mocks at its project page What does Rhino Mocks offers?
Why reinvent the wheel?
I hope you will enjoy using Rhino Mocks. In the first article, I had explained a little about mock objects, interaction base testing, state based test, etc. Here is a brief refreshment for those who don't want to read the full article.
So, what has changed?In version 2.0, I moved from the EasyMock.Net based code, which relied on Remoting's proxies; to inheritance approach using Castle.DynamicProxy. The resulting code base is much more flexible and easier to extend. While rebuilding it, I paid attention to several factors.
Rhino Mocks capabilities:
Let's get down to the code, okay? [I tried to create an example for this article, but the example was both contrived and didn't portrayed the possibilities correctly. So most of the code samples here were taken from NHibernate Query Analyzer tests and are "real code".] The purpose of mock objects is to allow you to test the interactions between components, this is very useful when you test code that doesn't lend itself easily to state base testing. Most of the examples here are tests to check that the save routine for a view is working as expected. The requirements for this method are:
So we have five tests here:
Trying to test that using state based testing is going to be both awkward and painful, using interaction based testing, it would be a breeze (other types of tests would be just as painful to test using interaction testing but easy using state testing.) That is enough talking, let's see some code:
[Test]
public void SaveProjectAs_CanBeCanceled()
{
using(MockRepository mocks = new MocksRepository())
{
IProjectView projectView = (IProjectView)projectView.CreateMock(typeof(IProjectView));
Project prj = new Project("Example Project");
IProjectPresenter presenter = new ProjectPresenter(prj,projectView);
Expect.On(projectView).Call(projectView.Title).Return(prj.Name);
Expect.On(projectView).Call(projectView.Ask(question,answer)).Return(null);
mocks.ReplayAll();
Assert.IsFalse(presenter.SaveProjectAs());
}
}
We create a MockRepository and create a mock project view, project and a project presenter, then we set the expectations. After we finished with setting up the expectations, we move to the replay state, and call the SaveProjectAs() method, which should return false if the user canceled the save process. This example clarify several key concepts with Rhino Mocks.
This is about as simple example as can be had, the real test move creating the MockRepository, the project, the view and presenter to the setup method, since they are require for each test. You can see that we expected two method to be called, with specific arguments, and that we set a result for each. This method uses parameter matching expectations, Rhino Mocks supports several more. More info: Method Calls. Ordered / Unordered:Method calls in Rhino Mocks can be ordered or unordered. The default state for a recorder is unordered recording, this means that during replay, methods can come at any order. If the recorder is changed to ordered, then the methods must be called in the exact same order as they were recorded. Here is a code sample:
[Test]
public void SaveProjectAs_NewNameWithoutConflicts()
{
using(mocks.Ordered())
{
Expect.On(projectView).
Call(projectView.Title).
Return(prj.Name);
Expect.On(projectView).
Call(projectView.Ask(question,answer)).
Return( newProjectName);
Expect.On(repository).
Call(repository.GetProjectByName(newProjectName)).
Return(null);
projectView.Title = newProjectName;
projectView.HasChanges = false;
repository.SaveProject(prj);
}
mocks.ReplayAll();
Assert.IsTrue(presenter.SaveProjectAs());
Assert.AreEqual(newProjectName,prj.Name);
}
In the above code example we ask Rhino Mocks to verify that the calls come in
the exact same order. Notice that we specify expectations on several mock
objects, and Rhino Mocks will handle the ordering between them. This mean that
if I set the project view title before I get a project from the repository, the
test will fail. In Rhino Mocks, the default is to use unordered matching, but it
support unlimited depth of nesting between ordered and unordered, this means
that you can create really powerful expectations. Here is a [Test]
public void MovingFundsUsingTransactions()
{
using(MockRepository mocks = new MockRepository())
{
IDatabaseManager databaseManager = mocks.CreateMock(typeof(IDatabaseManager));
IBankAccount accountOne = mocks.CreateMock(typeof(IBackAccount)),
accountTwo = mocks.CreateMock(typeof(IBankAccount));
using(mocks.Ordered())
{
Expect.On(databaseManager).
Call(databaseManager.BeginTransaction()).
Return(databaseManager);
using(mocks.Unordered())
{
accountOne.Withdraw(1000);
accountTwo.Deposit(1000);
}
databaseManager.Dispose();
}
mocks.ReplayAll();
Bank bank = new Bank(databaseManager);
bank.TransferFunds(accountOne,accountTwo,1000);
}
}
This code verify that the transfer of funds from one account to another is wrapped in a transaction, but the implementation is free to withdraw from the first account first, or to deposit into the second account first, both are legal, as long as both actions happens. The reverse is true as well, you may specified unordered sequence of ordered events (I want A then B then C to happen, and I want D then E then F to happen, but I don't care which sequence comes first). Ordering have two caveats:
The IDisposable pattern:The using(...) syntax & IDisposable are very convenient when it comes to make sure you're not forgetting to clean up resources, in Rhino Mocks, it's used in two places:
Method Calls:When Rhino Mocks intercept a call from a mocked object it determines if the call is expected by checking the method signature, the object this method was called on, whatever the expectation on this method match the arguments passed for the method. The matching of the mocked object and the method signature is not very interesting, but the expectations on a method call is. Rhino Mocks support the following expectations:
Callbacks:A callback is a user supplied delegate that is called whenever Rhino Mocks needs to evaluate whatever a method call is expected or not. This is useful in some scenarios when you want to do a complex validation on a method arguments, or have a complex interactions between objects that you need to mock in the tests. I added this feature because I want to test some threading code which had the semantics of: Start Job, and notify me when you're done. The only way to re-create that without bringing the real thread (and kill tests isolations) was to plug my own code during the replay state and call the tested object myself. Some things to consider before you decide to use callbacks:
If it so open to abuse, why add it?
The technical details - In order to be a valid callback, the callback must return a Boolean, and have the same arguments as the mocked methods. You register a delegate using the following code: IProjectRepository repository = (IProjectRepository) mocks.CreateMock(typeof(IProjectRepository));
IProjectView view = (IProjectView )mocks.CreateMock(typeof(IProjectView ));
Expect.On(view).(view.Ask(null,null)).Callback(new AskDelegate(DemoAskDelegateMethod)).Return(null);
Notice that you cannot change the return value for the method, but must pass it explicitly. Recursive Expectations:One final word of warning regarding callbacks. If your callback will initiate an action that cause another call on a mocked object, this will fail when mixed with Ordered(). The reason for that is that the framework cannot decide whatever to accept the call or not, and calling another mocked method while evaluating means that the currently expected call is the one still being evaluated. This will fail the test. Using Unordered(), it will work, since the second expectation is not dependant on the first one being accepted first. In short, Ordered() is not re-entrant :-) Method Options Interface:The IMethodOptions allows you to set various options on a method call. Here is an example of telling Rhino Mocks to ignore the arguments of a method: IProjectRepository repository = (IProjectRepository)mocks.CreateMock(typeof(IProjectRepository));
IProjectView view = (IProjectView ) mocks.CreateMock(typeof(IProjectView ));
Expect.On(view).(view.Ask(null,null)).IgnoreArguments().Return(null);
repository.SaveProject(null);
LastCall.On(repository).IgnoreArguments();
As you can see, we use the Expect.On().Call() for methods that has return values, and LastCall.On() for methods that return void to get the IMethodOptions interface. I find the Expect.On().Call() syntax a bit clearer, but there is no practical difference between the two. I would recommend using Expect wherever possible (anything that return a value). For properties setters, or methods returning void, the Expect syntax is not applicable, since there is no return value. Thus, the need for the LastCall.On(). The idea of Last Call is pervasive in the record state, you can only set the method options for the last call - even Expect.On().Call() syntax is merely a wrapper around LastCall.On(). Expect.On().Call() & LastCall.On() allows you to set the following options:
Note: For methods that return a value, you must specify either a return value or an exception to throw. You will not be able to continue recording or move to replay state otherwise. Note II: Method chaining really makes writing this cod easier. Constraints:Constraints are a way to verify that a method arguments match a certain criteria. Rhino Mocks include a number of built in constraints, and allows you to define you own custom one, which will integrate cleanly into the framework. You specify constraints on method arguments using the following syntax: Expect.On(view).
(view.Ask(null,null)).Return(null).
Constraints(
Text.StartsWith("Some"),
Text.EndsWith("Text"));
You need to pass the exact same number of constraints as the number of arguments of the method. When a method is called during replay state, Rhino Mocks evaluate each constraint against the parameter in the same index, and accept the method if all the constraints were met. As a note, I got the idea of the current constraints syntax from NMock2, it is much leaner approach to create constraints. Rhino Mocks built in constraints: Rhino mocks ships with the following constraints:
Operator Overloading warning: Pay attention to operator precedence if you are using several operators in a single statement, otherwise you may get unexpected results. Creating custom constraints: Creating you custom constraint is easy, just derive a class from AbstractConstraint and return true from the Eval() method. Setup Result:Sometimes you have a method on your mocked object where you don't care how / if it was called, you may want to set a return value (or an exception to be thrown), but for this specific test, you just don't care. For example, you may have some interaction that you already verified, or you are testing some other class and just need to get the right value from a method. The way to do it in Rhino Mocks is to use SetupResult.On().Call(). Here is the code: [Test]
public void SetupResultUsingOrdered()
{
SetupResult.On(demo).Call(demo.Prop).Return("Ayende");
using(mocks.Ordered())
{
demo.VoidNoArgs();
LastCall.On(demo).Repeat.Twice();
}
mocks.ReplayAll();
demo.VoidNoArgs();
for (int i = 0; i < 30; i++)
{
Assert.AreEqual("Ayende",demo.Prop);
}
demo.VoidNoArgs();
}
When we use SetupResult.On().Call() we tell Rhino Mocks: "I don't care about this method, it need to do X, so just do it, but otherwise ignore it." Using SetupResult.On().Call() completely bypass the expectations model in Rhino Mocks. In the above example, we define demo.Prop to return "Ayende", and we can all it no matter in what the currently expected method is. What about methods returning void? Note: If you want to have a method that can repeat any number of time, but still obey ordering, you can use: LastCall.On().Repeat.Times(0,int.MaxValue), this does obey all the normal rules. Mocking classes:Rhino Mocks supports mocking concrete classes as well as interfaces. In fact, it can even mock classes that doesn't have a default constructor! To mock a class, simply pass its type to MockRepository.CreateMock() along with any parameters for the constructor. [Test]
public void AbuseArrayList()
{
using(MockRepository mocks = new MockRepository())
{
ArrayList list = (ArrayList)mocks.CreateMock(typeof(ArrayList));
Expect.On(list).Call(list.Capacity).Return(999);
mocks.ReplayAll();
Assert.AreEqual(999,list.Capacity);
}
}
If you want to call the non default constructor, just add the parameters after the type. Like this: ArrayList list = (ArrayList)mocks.CreateMock(typeof(ArrayList),500);
Some things to be aware of:
Limitations:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||