views:

35

answers:

1

I did a search on SO and looks like this question gets asked quite often. I have been able to get the mocks working and I'm also able to execute OnActionExecuted() without any issues. Here's my Unit Test. The commented lines are the ones that fail and I'm sure I'm not mocking the right type.

        //Arrange
        //var viewResult = new ViewResult();
        var filterContextMock = new Mock<ActionExecutedContext>();
        var routeData = new RouteData();
        var httpContextMock = new Mock<HttpContextBase>();

        routeData.Values["data"] = "Mock data";
        var requestContext = new RequestContext(httpContextMock.Object, routeData);

        var controller = new FakeController();
        controller.ControllerContext = new ControllerContext(requestContext, controller);

        filterContextMock.Setup(f => f.RouteData).Returns(routeData);
        filterContextMock.Setup(f => f.Controller).Returns(controller);
        //filterContextMock.Setup(f => f.Result).Returns(viewResult);

        //Act
        var wrapFilterAttribute = new WrapFilterAttribute();
        wrapFilterAttribute.OnActionExecuted(filterContextMock.Object);

Here is my Action Filter.

public class WrapFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var view = (ViewResultBase)filterContext.Result;

        if (view != null)
        {
            BaseViewModel viewModel = (BaseViewModel)view.ViewData.Model ?? new BaseViewModel();
            viewModel.Wrap = new WrapperFactory().GetWrap();
        }

        base.OnActionExecuted(filterContext);
    }
}

The issue I'm facing here is filterContext.Result always comes in as EmptyResult. I'd like to push in a hydrated ViewResult instead. Any ideas how I can accomplish this?

Many thanks!

+3  A: 

First let's start by fixing your action filter as currently the code looks bad and those castings might bring you headaches:

public class WrapFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var view = filterContext.Result as ViewResultBase;
        if (view != null)
        {
            // the controller action returned a ViewResultBase
            var viewModel = view.ViewData.Model as BaseViewModel;
            if (viewModel != null)
            {
                // the model passed to the view was derived from
                // BaseViewModel so we can safely update the Wrap
                // property
                viewModel.Wrap = new WrapperFactory().GetWrap();
            }
        }
        base.OnActionExecuted(filterContext);
    }
}

And the unit test:

// arrange
var sut = new WrapFilterAttribute();
var filterContextMock = new Mock<ActionExecutedContext>();
var viewResultMock = new Mock<ViewResultBase>();
filterContextMock.Object.Result = viewResultMock.Object;
var viewModel = new BaseViewModel();
viewResultMock.Object.ViewData.Model = viewModel;

// act
sut.OnActionExecuted(filterContextMock.Object);

// assert
// TODO: assert something on the viewModel.Wrap property like 
// for example that it has been initialized

Remark: Your action filter has a strong dependency on WrapperFactory class. This is not good. A further improvement would be to abstract this functionality into an interface which would be injected into the constructor of the action filter. This would allow you further separation of concerns between different layers of your application.

Darin Dimitrov
Thanks for refactoring my crappy code Darin. This worked for me. Thanks again :)
Praveen
Your comment about injecting WrapperFactory into the action filter constructor makes me curious. How would you accomplish sending in an instance of WrapperFactory via WrapFilterAttribute decoration? AFAIK, attributes require compile-time values (constants etc.).
Praveen
@Praveen, here's an example of how to achieve [constructor injection with action filters](http://iridescence.no/post/Constructor-Injection-for-ASPNET-MVC-Action-Filters.aspx). It's a bit hacky but that's the only way for the moment. This has been improved in ASP.NET MVC 3.
Darin Dimitrov