Advance Mocking - Partial mocks and Do()

time to read 3 min | 482 words

Kyle has just posted about the MvcContrib.TestHelper project:

The CreateController method on my TestControllerBuilder uses Castle's DynamicProxy to create the controller. It also creates a ControllerContext using dynamic mocks from Rhino Mocks similar to the way Haackselman have already showed you and wires it up to the controller.

An interceptor is also attached to the controller to intercept calls to RenderView and RedirectToAction. (Actually, it intercepts all calls but only these two have special handling.) Calls to either method will populate an appropriate object on the builder class. In the example above, I'm using the RedirectToActionData object.

I'll leave aside the need to get to that level when you want to test brand new framework and code. I think that this is a serious flaw, but that is not the subject of this post. The use of dynamic proxy and interceptors caught my eye, because they aren't actually needed. Rhino Mocks can do it for you. Let us see how it is done right now.

public T CreateController<T>() where T : Controller
{
      ProxyGenerator generator = new ProxyGenerator(); //Castle DynamicProxy
      Controller controller = (Controller)generator.CreateClassProxy(typeof(T), new ControllerInterceptor(this));

      ControllerContext controllerContext = new ControllerContext(HttpContext, RouteData, controller);
      controller.ControllerContext = controllerContext;
     
      typeof(T).GetProperty("TempData").SetValue(controller, TempDataDictionary, null);

      return controller as T;
}

The controller interceptor can be found here. Let us take out the dynamic proxy part and use Rhino Mocks to handle it. In this case, it will look like this:

public T CreateController<T>(params object[] ctorArgs) where T : Controller
{
	Controller controller = _mocks.PartialMock<T>();
	
	var renderViewDataParams = new Type[]{typeof(string),typeof(string), typeof(object));
	typeof(Controller).GetMethod("RenderViewData", renderViewDataParams)
		.Invoke(controller, new [] { null, null, null });
	LastCall.IgnoreArguments().Repeat.Any()
		.Do( (RenderViewDataDelegate)delegate(string viewName, string masterName, object viewData)
	{
		RenderViewData = new RenderViewData
		{
			ViewName = viewName, 
			MasterName = masterName, 	
			ViewData = viewData
		};
	});
	
	typeof(Controller).GetMethod("RedirectToAction", new []{typeof(RouteValueDictionary}})
		.Invoke(controller, new []{null});
	LastCall.IgnoreArguments().Repeat.Any()
		.Do( (RedirectToActionDelegate) delegate(RouteValueDictionary route)
	{
		RedirectToActionData = new RedirectToActionData
		{
			ActionName = Convert.ToString(values.ContainsKey("action") ? values["action"] : ""),
			ControllerName = Convert.ToString(values.ContainsKey("controller") ? values["controller"] : "")
		};
	});

	_mocks.Repaly(controller);
	
	return controller;
}

A lot of the ugliness here is the need to make calls with reflection, because they aren't exposed externally. Ignoring that, it is really a trivial exercise.

Just to point out, I added this functionality to Rhino Mocks a long time ago, specifically to enable the Extract and Override scenarios. This is a technique used for legacy code testing, it should be frowned upon in most green field scenarios.