Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing an ActionFilter - correctly setting up the ActionExecutingContext

In a custom ActionFilter, I want check the attributes on the controller action that will be executed. Running through a small test application, the following works when launching the app in the asp.net development server-

public class CustomActionFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var someAttribute = filterContext.ActionDescriptor
                                    .GetCustomAttributes(typeof(SomeAttribute), false)
                                    .Cast<SomeAttribute>()
                                    .SingleOrDefault();

        if (someAttribute == null)
        {
            throw new ArgumentException();
        }

        // do something here
    }

    public override void OnActionExecuted(ActionExecutingContext filterContext)
    {
        // ...
    }
}

An action method without SomeAttribute throws an ArgumentException and conversely, an action method with SomeAttribute does not. So far so good.

Now I would like to set up some unit tests for the ActionFilter, but how can I set up the action method upon which the OnActionExecuting method should run in the unit test? Using the following code doesn't find SomeAttribute on the action method which will be executed. Is the test set up correctly? Have I not arranged something correctly in the test? To clarify, the test is not complete but I'm not sure what I've missed such that someAttribute in OnActionExecuting in the test is null

    [TestMethod]
    public void Controller_With_SomeAttribute()
    {
        FakeController fakeController =
            new FakeController();

        ControllerContext controllerContext = 
            new ControllerContext(new Mock<HttpContextBase>().Object, 
                                  new RouteData(), 
                                  fakeController);

        var actionDescriptor = new Mock<ActionDescriptor>();
        actionDescriptor.SetupGet(x => x.ActionName).Returns("Action_With_SomeAttribute");

        ActionExecutingContext actionExecutingContext =
            new ActionExecutingContext(controllerContext,
                                       actionDescriptor.Object, 
                                       new RouteValueDictionary());

        CustomActionFilterAttribute customActionFilterAttribute = new CustomActionFilterAttribute ();
        customActionFilterAttribute.OnActionExecuting(actionExecutingContext);
    }

    private class FakeController : Controller
    {
        [SomeAttribute]
        ActionResult Action_With_SomeAttribute()
        {
            return View();
        }
    }
like image 879
some-one Avatar asked Mar 02 '10 22:03

some-one


1 Answers

Since the ActionDescriptor property of ActionExecutingContext is virtual, you can just override that and provide your own implementation of ActionDescriptor.

Here are two tests that verify the two branches through the current implementation of OnActionExecuting:

[ExpectedException(typeof(ArgumentException))]
[TestMethod]
public void OnActionExecutingWillThrowWhenSomeAttributeIsNotPresent()
{
    // Fixture setup
    var ctxStub = new Mock<ActionExecutingContext>();
    ctxStub.Setup(ctx => ctx.ActionDescriptor.GetCustomAttributes(typeof(SomeAttribute), false))
        .Returns(new object[0]);

    var sut = new CustomActionFilterAttribute();
    // Exercise system
    sut.OnActionExecuting(ctxStub.Object);
    // Verify outcome (expected exception)
    // Teardown
}

[TestMethod]
public void OnActionExecutingWillNotThrowWhenSomeAttributeIsPresent()
{
    // Fixture setup
    var ctxStub = new Mock<ActionExecutingContext>();
    ctxStub.Setup(ctx => ctx.ActionDescriptor.GetCustomAttributes(typeof(SomeAttribute), false))
        .Returns(new object[] { new SomeAttribute() });

    var sut = new CustomActionFilterAttribute();
    // Exercise system
    sut.OnActionExecuting(ctxStub.Object);
    // Verify outcome (no exception indicates success)
    // Teardown
}
like image 169
Mark Seemann Avatar answered Nov 15 '22 07:11

Mark Seemann