Remoting Proxy Support for Rhino Mocks
Summary
In October 2007 I added remoting proxy support to Ayende Rahien's
Rhino Mocks library.
This allows Rhino Mocks to mock any type derived from MarshalByRefObject
,
e.g. AppDomain
or Control
. This feature is available starting from
Rhino Mocks 3.3.
Some History
While I was working in Bloomberg, we used a "ProxyMock" extension to the
NMock library. This extension was written by
my friend Levy Haskell. Proxy mock could mock any MarshalByRefObject
by using .NET remoting mechanisms. Current status of ProxyMock
is not clear to me. As far as I know, it has been a part of the NMock code base
at some point, but it never made it to a public release of NMock.
After I left Bloomberg, I could no longer use ProxyMock. My friends
in Finetix introduced me to Rhino Mocks
by Ayende Rahien.
I instantly fell in love with that library, but I missed the ability to
mock MarshalByRefObject
s.
Unmockable Classes
From time to time I ran into difficult situations with my tests. Let's say I have a view with an Infragistics grid in it. The Infragistics grid fires an event each time a grid row receives new data. Subscribers may intercept this event and change the row appearance based on the new data, e.g. change its background color. The view class passes row initialization event to the presenter, and the presenter tells the view which background color to use.
class InitializeRowEventArgs { UltraGridRow GridRow; // from Infragistics grid library DataRow DataRow; } delegate void InitializeRowHandler(object sender, InitializeRowEventArgs row); interface IView { event InitializeRowHandler RowInitialized; void SetBackgroundColor( UltraGridRow gridRow, Color color ); } class Presenter { IView _view; ... _view.RowInitialized += OnRowInitialized; ... void OnRowInitialized( object sender, InitializeRowEventArgs args ) { _view.SetBackgroundColor(args.GridRow, GetColorForRow(args.DataRow)); } }
The presenter is not really interested in the guts of the UltraGridRow
.
In this particular example the grid row is completely opaque for the presenter.
From the other hand, the presenter needs to have a grid row object.
Otherwise, it would not be able to tell the view which row to color.
When I write a test for this code, I need to come up with a grid row object, pass it to the presenter, and make sure the row initialized is the row colored.
[TestFixture] public class PresenterTest { MockRepository _mocks; [SetUp] public void Setup() { _mocks = new MockRepository(); } [Test] public void ColorRowOnInit() { IView view = _mocks.CreateMock<IView>(); view.RowInitialized += null; IEventRaiser rowInitialized = LastCall.IgnoreArguments().GetEventRaiser(); UltraGridRow fakeGridRow = what goes here? DataRow dataRow = some sample data; RowInitializedEventArgs args = new RowInitializedEventArgs(fakeGridRow, dataRow); view.SetBackgroundColor( fakeGridRow, Color.Red ); // we expect it to be red _mocks.ReplayAll(); Presenter = new Presenter(view); rowInitialized.Raise(view, args); _mocks.VerifyAll(); } }
The problem is: I cannot easily create an UltraGridRow
object:
it does not have public constructors. In order to get a bona fide UltraGridRow
I will have to create a real Infragistics grid control, properly set it up, and give it
a real data source with at least one data row. Too much trouble for a little test.
I cannot mock an UltraGridRow
either. It is not an interface, it's a real class.
Rhino Mocks mocks real classes by dynamically creating derived classes with overriden
virtual functions. If I cannot create an UltraGridRow
, creating an object
of a derived type is also going to be a problem.
Remoting to The Resque
Fortunately, UltraGridRow
, like many other graphical widgets, ultimately
derives from MarshalByRefObject
. It means, I can throw some remoting at it.
Here is how typical remote object works:
Transparent Proxy
There is no real object on the client side. A special entity called "transparent proxy"
takes its place. Transparent proxy is a very peculiar object. Given any type T that derives
from MarshalByRefObject
, .NET framework can construct a "fake" instance
of that type out of thin air, bypassing any constructors and initializers.
Obviously, this cannot be done in regular C# (or even IL).
Transparent proxies are created by the "magic" method
RemotingServices.CreateTransparentProxy()
.
This method is marked extern
and is implemented in unmanaged code.
It is also internal. Mere mortals call this method indirectly via
RealProxy.GetTransparentProxy()
.
Since transparent proxy has no "meat", it cannot do any real work.
It forwards all method calls to the real proxy object by calling
RealProject.Invoke()
method. The real proxy is supposed
to package input parameters, transfer them over the wire, receive return
message, process the results and convert them either to a return value
or to an exception.
Real Proxy
RealProxy
class from System.Runtime.Remoting.Proxies
namespace
is a base class for all real proxies. RealProxy
class is abstract.
Actual .NET remoting proxy is System.Runtime.Remoting.Proxies.RemotingProxy
.
However, we are free to create our own real proxies that do any other kind of marshalling.
Mocking Proxy
If you read a description of transparent proxy, it looks like it was specifically invented to create mocks. It can construct a fake object out of thin air and forward all the calls to a special interceptor - the real proxy.
The only problem was to create such an interceptor.
In the end of July 2007 I asked Ayende whether he would be interested
in such an interceptor, and he said yes. In October 2007 I finally got
the time to actually write it. Ayende and I went over a couple of
iterations and voila - Rhino Mocks 3.3 has ability to mock
MarshalByRefObject
s.
Rhino Mocks internally uses interception framework from the
Castle project.
The framework defines IInvocation
interface to represent
a single method call and IInterceptor
interface
to represent a call interceptor. Castle does not define what specifically
the interceptor should do with the calls. Rhino Mocks defines
RhinoInterceptor
class that handles mock expectations
and replay.
I needed to write an adapter that would forward
calls from a .NET remoting proxy to an IInterceptor
.
This proved to be relatively straightforward, but there were some
tricky pitfalls along the path. You can see the source code in the Rhino Mocks
SVN repository.
One must keep in mind that absolutely all method
calls are forwarded to real proxy. This includes things like
GetType()
, GetHashCode()
, and Equals()
.
If you want to put the proxy in a container (and Rhino Mocks does want
that), these methods cannot be left out. They must
be handled on the proxy level, and they must be handled correctly.
Communicating with The Real Proxy
Another tricky problem was to retrieve information about
the real proxy when you have only an instance of transparent proxy.
In particular, I needed to extract an IMockedObject
reference held by the Rhino Mocks real proxy. IMockedObject
is an internal Rhino Mocks class used for housekeeping.
The problem is: transparent proxy can be of any type
(as long as it derives from MarshalByRefObject
),
so all I can rely on are the methods of the Object
class.
There are not many to choose from:
public class Object { public virtual bool Equals(object obj); public virtual int GetHashCode(); public Type GetType(); public virtual string ToString(); }
Out of these, GetHashCode()
, GetType()
and ToString()
don't look very interesting, because a) they don't take any parameters and b) they return
some fixed type. I would not be able to retrieve an IMockedObject
reference
through any of these there methods. This leaves us with Equals()
.
Equals()
can take object of any type, and, if necessary, it can even modify
that object to store return data. So, I can use Equals()
as a two-way communication mechanism that can tell the real proxy what I want and get the data back.
Of course, this is not what Equals()
was invented for.
Normally, Equals()
is not supposed to modify its argument.
Ayende called this technique "an evil hack".
However, this was the only feasible way to extract data from the real proxy.
I even added some object oriented flavor to that evil hack - something along the lines of the visitor pattern. I defined a special interface:
interface IRemotingProxyOperation { void Process(RemotingProxy proxy); }
The real proxy's Equals()
handle checks whether the object passed is
of type IRemotingProxyOperation
, and if yes, calls its Process()
method, passing the proxy as the argument:
private bool HandleEquals(IMethodMessage mcm) { object another = mcm.Args[0]; if (another == null) return false; if (another <is IRemotingProxyOperation) { ((IRemotingProxyOperation)another).Process(this); return false; } return ReferenceEquals(GetTransparentProxy(), another); }
This allows to add new proxy processing operations without modifying proxy code.
Currently only two operations are defined: checking whether particular object is
a transparent proxy for Rhino Mocks remoting proxy, and retrieving the
IMockedObject
reference
(RemotingMockGenerator.cs).
public static bool IsRemotingProxy(object obj) { if (obj == null) return false; RemotingProxyDetector detector = new RemotingProxyDetector(); obj.Equals(detector); return detector.Detected; } public static IMockedObject GetMockedObjectFromProxy(object proxy) { if (proxy == null) return null; RemotingProxyMockedObjectGetter getter = new RemotingProxyMockedObjectGetter(); proxy.Equals(getter); return getter.MockedObject; }
Using Remoting Proxy
You don't need to do anything special to use remoting proxy.
This is why this paragraph is so short. :-)
The CreateMock<T>()
method automatically detects
the kind of type you are trying to mock and uses remoting proxy
if the type is a MarshalByRefObject
. E.g.
MockRepository mocks = new MockRepository(); UltraGridRow row = mocks.CreateMock<UltraGridRow>(); // uses remoting mock
Conclusion
Adding remoting proxies to Rhino Mocks extends the set of types it can mock.
Many "difficult" types derive from MarshalByRefObject
.
In particular, System.Windows.Forms.Control
and all derived classes
are marshalled by reference and thus can be mocked using remoting proxies.
Most Infragistics types (in the Windows Forms flavor of Infragistics) are also
marshalled by references, and also can be mocked.
Some people say that this shifts Rhino Mocks in the direction of TypeMock, and this is a bad thing. That might be the case in theory, but in practice remoting mocks allow me to unit test things I could not unit test before. So, it must be a good thing, at least in some cases.
Besides, working on this project was a lot of fun, and I learnt a lot about internals of Rhino Mocks, reflection on generic types, etc. I hope you will enjoy the end result of this project no less than I enjoyed working on it.