Ayende @ Rahien

It's a girl

Don't like visibility levels, change that

This came up with respect to MS MVC. The MS MVC team has decided to make some methods protected, and that makes testing using Rhino Mocks a bit of a pain.

Let us assume that I want test that the call to ProductController.Index() will render the "index.aspx" view. I can do something like:

[Test]
public void ShouldAskToRenderTheIndexAspxView()
{
	MockRepository mocks = new MockRepository();
	ProductController controller = mocks.PartialMock<ProductController>();
	using(mocks.Record())
	{
		controller.RenderView("index.aspx"); // <<-- Compiler error here, RenderView is protected!
	}

	using (mocks.Playback())
	{
		controller.Index();
	}
}

Problem, part of the Rhino Mocks design goal was the use of the compiler and tools as verifiers. I explicitly do not want to have strings in my code, and I put them only where I have no other choice. However, since Rhino Mocks by runtime subclassing, there is no issue with reflection calls...

This means that we can get around this limitation by using the following extension method:

public static void RenderView(this Controller self, string action)
{
	typeof(Controller).GetMethod("RenderView").Invoke(self, new object[] { action} );
}

This is it, it will compile and work now.

Comments

Haacked
12/13/2007 09:29 AM by
Haacked

Nice! I need to update my post with this technique.

Thomas Gravgaard
12/13/2007 10:39 AM by
Thomas Gravgaard

Just one slight change is needed to the code. The RenderView extension method has to look like this since the original method is protected. But is is still a very neat technique. Seems like MVC is drawing out a lot of the cool stuff you can do with extension methods:

    public static void RenderView(this Controller self, string action)

    {

        typeof(Controller).GetMethod("RenderView", 

            System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, 

            null, 

            new Type[] { typeof(String) },

            null

            ).Invoke(self, new object[] { action });

    }
Grimace of Despair
12/13/2007 10:45 AM by
Grimace of Despair

Could someone hint the guys at Microsoft that their code "protection" through internal/private/protected members all over the place might just not be such a good idea in the light of extensibility?

John Chapman
12/13/2007 01:23 PM by
John Chapman

This may sound like blasphemy, but why doesn't Rhino mocks just add the ability to reference a method by name instead of having to call the method when registering it in the framework in order to handle non-public members.

I love the way it works for public methods, but if I need to add an extension method and then use reflection to wire up my mock I may as well be using the framework instead since I'll have the weakly typed issues anyway.

I would just feel dirty using extension methods in this way. Plus it seems like extra unnecessary steps every time you need to do something like this.

Ayende Rahien
12/13/2007 01:31 PM by
Ayende Rahien

John,

I am accepting patches :-)

H&#229;kan Forss
12/13/2007 02:02 PM by
Håkan Forss

I'm a little into John's approach but with a twist.

Wouldn't it be possible to have Rhino mock expose all protected methods as public on creation of a partial mock

Ayende Rahien
12/13/2007 02:04 PM by
Ayende Rahien

Certainly,

But you won't get the compile time access to that, RhinoMocks works at run time.

Jimmy Bogard
12/13/2007 02:49 PM by
Jimmy Bogard

Just curious, would you recommend this mechanism over something like "Subclass and override"? For example:

public class TestController : Controller

{

public new virtual void RenderView(string action)

{

    base.RenderView(action);

}

}

Slightly more annoying, but with compile-time safety.

Vijay Santhanam
12/27/2007 12:00 PM by
Vijay Santhanam

Haack has an interesting post on this topic:

http://haacked.com/archive/2007/12/09/writing-unit-tests-for-controller-actions.aspx

He recommends the Test Specific Subclass if you have protected methods. I think he implies in his post that most protected virtual methods will become public to assist TDD - which would ideally solve this problem.

Comments have been closed on this topic.