Click here to Skip to main content
Click here to Skip to main content

Remoting Proxy Support for Rhino Mocks

, 17 Dec 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
Rhino Mocks version 3.3 can mock any MarshalByRefObject.

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. ProxyMock could mock any MarshalByRefObject by using .NET Remoting mechanisms. The 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 MarshalByRefObjects.

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 the 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 overridden 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 Rescue

Fortunately, UltraGridRow, like many other graphical widgets, ultimately derives from MarshalByRefObject. It means, I can throw some Remoting at it. Here is how a typical remote object works:

remoting proxy picture

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, the .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 a transparent proxy has no "meat", it cannot do any real work. It forwards all method calls to the real proxy object by calling the RealProject.Invoke() method. The real proxy is supposed to package input parameters, transfer them over the wire, receive a return message, process the results, and convert them either to a return value or to an exception.

Real Proxy

The RealProxy class from the System.Runtime.Remoting.Proxies namespace is a base class for all real proxies. The RealProxy class is abstract. The 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.

Real Proxy and derived classes

Mocking Proxy

If you read a description of a 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 the ability to mock MarshalByRefObjects.

Rhino Mocks internally uses an interception framework from the Castle project. The framework defines the IInvocation interface to represent a single method call, and the IInterceptor interface to represent a call interceptor. Castle does not define what specifically the interceptor should do with the calls. Rhino Mocks defines the 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.

Mocking proxy

One must keep in mind that absolutely all method calls are forwarded to the 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 the 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: a 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:

  1. they don't take any parameters, and
  2. they return some fixed type.

I would not be able to retrieve an IMockedObject reference through any of these three methods. This leaves us with Equals(). Equals() can take an 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 the proxy code. Currently, only two operations are defined: checking whether a particular object is a transparent proxy for the 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 the Remoting Proxy

You don't need to do anything special to use the Remoting proxy. This is why this paragraph is so short. Smile | :) The CreateMock<T>() method automatically detects the kind of type you are trying to mock, and uses the 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 marshaled by reference, and thus can be mocked using Remoting proxies. Most Infragistics types (in the Windows Forms flavor of Infragistics) are also marshaled 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 the 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.

History

  • Last updated: December 17, 2007.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ivan Krivyakov
Technical Lead Thomson Reuters
United States United States
Ivan is a hands-on software architect/technical lead working for Thomson Reuters in the New York City area. At present I am mostly building complex multi-threaded WPF application for the financial sector, but I am also interested in cloud computing, web development, mobile development, etc.
 
Please visit my web site: www.ikriv.com.

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.1411023.1 | Last Updated 17 Dec 2007
Article Copyright 2007 by Ivan Krivyakov
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid