10

I've overridden my controller's OnActionExecuting method to set some internal state based on the executing filterContext. How do I test this? The method itself is protected so I assume I'll have to go higher up in the call stack.

What code do I need to test this?

I'm using mvc RC 1.

Edit: I'm also using nunit.

Thanks

Dane O'Connor
  • 75,180
  • 37
  • 119
  • 173

3 Answers3

15

You need to add and use a Private Accessor. Right click in your controller class and choose Create Private Accessors from the menu and add them to your test project. Once in your test project, create your controller, then create an accessor for it. The method should be available on the accessor. Here's a sample test from my own code:

/// <summary>
///A test for OnActionExecuting
///</summary>
[TestMethod()]
[ExpectedException( typeof( InvalidOperationException ) )]
public void OnActionExecutingWindowsIdentityTest()
{
    var identity = WindowsIdentity.GetCurrent();
    WindowsPrincipal principal = new WindowsPrincipal( identity );
    var httpContext = MockRepository.GenerateStub<HttpContextBase>();
    httpContext.User = principal;

    var actionDescriptor = MockRepository.GenerateStub<ActionDescriptor>();

    RouteData routeData = new RouteData();

    BaseController controller = new BaseController();
    BaseController_Accessor accessor = new BaseController_Accessor( new PrivateObject( controller ) );
    ControllerContext controllerContext = MockRepository.GenerateStub<ControllerContext>( httpContext, routeData, controller );

    ActionExecutingContext filterContext = new ActionExecutingContext( controllerContext, actionDescriptor, new Dictionary<string, object>() );

    accessor.OnActionExecuting( filterContext );

}

EDIT: If you aren't using MSTest for your unit tests, you may have to generate the accessors by hand. Essentially, you make a wrapper class that exposes the private/protected methods of the class under test via equivalent public methods, pass an instance of the class under test to the wrapper, and then use reflection from the wrapper class to invoke the private/protected method on the class under test.

   public class MyClass
   {
       protected void DoSomething( int num )
       {
       }
   }

   public class MyClass_accessor
   {
       private MyClass privateObj;

       public MyClass_accessor( MyClass obj )
       {
           this.privateObj = obj;
       }

       public void DoSomething( int num )
       {
           MethodInfo info = privateObj.GetType()
                                       .GetMethod("DoSomething",
                                                   BindingFlags.NonPublic
                                                   | BindingFlags.Instance );

           info.Invoke(obj,new object[] { num });
       }
    }
tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • I only ask because I don't see the 'add accessor' context button and doing a google search brings me to some ms test specific stuff. – Dane O'Connor Feb 26 '09 at 18:21
  • Yes, I think it is. I suppose that it requires at least VS Pro for it to be there. You can create one by hand though and use reflection to invoke the appropriate method on the underlying private object. – tvanfosson Feb 26 '09 at 19:36
  • There's nothing really magic about the accessors added via MSTest, they just use reflection. They are nice in that you don't have to generate all the code. I've given an example in my answer. Note that you could also invoke the method directly in your tests via reflection, but it may violate DRY. – tvanfosson Feb 26 '09 at 20:09
  • Thanks for the help. I have VS Pro. I was able to generate accessors by right clicking and going to "Create Unit Tests..." and select the private method I wanted to test. This created an MSTest project with the appropriate accessor generated. – Dane O'Connor Feb 26 '09 at 20:40
  • Rather than go through those steps each time though I'll just make the accessors by hand like you described. Thanks for the example. – Dane O'Connor Feb 26 '09 at 20:41
4

I recently had a similar problem and could not find satisfying solution. So I created my own helper function that invokes OnActionExecuted and OnActionExecuting. See code here http://mkramar.blogspot.com.au/2012/06/onactionexecuting-and-onactionexecuted.html

m_kramar
  • 410
  • 4
  • 15
  • Notice how the accepted answer includes *code and explanation*, and lacks any self-promotional links? That is the pattern for acceptable answers here. – Andrew Barber Nov 01 '12 at 04:09
  • I did not get the other to work. But your code work.Maybe I should edit your answere and add your code. I combined your code with http://stackoverflow.com/a/10316899/648076. Now the test looks real nice. – Patrik Lindström Jan 06 '13 at 16:59
0

I was trying to do this, but I actually wanted to test the outcome of the custom attribute as it applied to the actual controller. In our case we had an authorization attribute that set properties on the controller, the controller then used the properties. Our code looks something like this:

// Create the controller to test
PortalController controller = new PortalController();
var method = typeof(PortalController);
var attribute = method.GetCustomAttributes(typeof(OrganizationContextFilter),true).Cast<OrganizationContextFilter>().SingleOrDefault();

// Set the controller Context with our fake objects on it
controller.ControllerContext = this.GetDefaultControllerMock(controller);

// Execute the Organization Context Filter
var actionDescriptor = new Mock<ActionDescriptor>();
var context = Mock.Get(actionDescriptor.Object);
context.Setup(s => s.ControllerDescriptor).Returns(new Mock<ControllerDescriptor>().Object);

// Use the current controller Context
ActionExecutingContext filterContext = new ActionExecutingContext( controller.ControllerContext, actionDescriptor.Object, new Dictionary<string, object>() );
attribute.OnActionExecuting(filterContext);

// We have to use this one, because it has the result of the Attribute execution
PortalController pc = filterContext.Controller as PortalController;
ActionResult result = pc.MethodToTest(); // Call the controller that had OnActionExecuting results

The benefit of this is that we actually execute the custom MVC attribute on the controller we are actually testing. This both exercises the custom attribute code and tests the controller in a situation that is more like the "real world".

ProVega
  • 5,864
  • 2
  • 36
  • 34
  • Just a quick question 2 years after the fact - how does one set a controller property through a derived attribute? – VisualBean Nov 20 '15 at 21:50