Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test ActionFilterAttribute

I'm looking to test an ActionFilterAttribute in a .NET Core 2.0 API project and wondering the best way to go about it. Note, I'm not trying to test this through a controller action, merely test the ActionFilterAttribute itself.

How might I go about testing this:

    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (!context.ModelState.IsValid)
            {
                context.Result = new BadRequestObjectResult(context.ModelState);
            }
        }
    }
like image 457
ovation22 Avatar asked Sep 11 '17 19:09

ovation22


People also ask

How to demonstate the different features of action filters?

To demonstate the different features of action filters, different custom ActionFilters have been defined. Each filter writes to the output window. public class Action1DebugActionWebApiFilter : ActionFilterAttribute

What is the difference between Action Filter and result filter?

You can use an action filter, for instance, to modify the view data that a controller action returns. Result filters contain logic that is executed before and after a view result is executed. For example, you might want to modify a view result right before the view is rendered to the browser.

Are actionfilters for web API the same as filters for MVC?

Filters for Web API are not the same as filters for MVC. The Web API filters are found in the System.Web.Http.Filters namespace. To demonstate the different features of action filters, different custom ActionFilters have been defined.

Is the Order of the actionfilters on the action method sorted?

Only groups of filters and in this example, all filters from group IActionFilter are overrided. This example is the same as example 1, except the action method has 4 different ActionFilters defined. As shown in the results, the order of the ActionFilters on the action method is NOT sorted!


2 Answers

Create an instance of the context pass it to the filter and assert the expected behavior

For example

[TestClass]
public class ValidateModelAttributeTest {
    [TestMethod]
    public void Invalid_ModelState_Should_Return_BadRequestObjectResult() {
        //Arrange
        var modelState = new ModelStateDictionary();
        modelState.AddModelError("", "error");
        var httpContext = new DefaultHttpContext();
        var context = new ActionExecutingContext(
            new ActionContext(
                httpContext: httpContext,
                routeData: new RouteData(),
                actionDescriptor: new ActionDescriptor(),
                modelState: modelState
            ),
            new List<IFilterMetadata>(),
            new Dictionary<string, object>(),
            new Mock<Controller>().Object);

        var sut = new ValidateModelAttribute();

        //Act
        sut.OnActionExecuting(context);

        //Assert
        context.Result.Should().NotBeNull()
            .And.BeOfType<BadRequestObjectResult>();
    }
} 
like image 83
Nkosi Avatar answered Oct 13 '22 15:10

Nkosi


Here is a real life example, where I also access the method info and params inside the action filter attribute:

Suppose I have a Controller Method with ActionAttribute like this:

 public class HomeController : Controller
    {
    ...
    [FeatureFlagActionAtrribute("user", new String[] { "Feature1" })]
    public IActionResult DoSomethingWithFilterAction(String user)
        {...}
    }

The http call would be something like this:

/Home/DoSomethingWithFilterAction?user="user1"

Now, I want to test the ActionAttribute FeatureFlagActionAtrribute in such a context.

If you would apply above suggestion to this example, it would look like this (worked for me, at least)

 var methodInfoDoSomethingWithFilterAction = typeof(HomeController).GetMethod(nameof(HomeController.DoSomethingWithFilterAction));
    var httpContext = new DefaultHttpContext();
    var routeData = new RouteData();
    FeatureFlagActionAtrribute FeatureFlagActionAtrributeFilter = methodInfoDoSomethingWithFilterAction.GetCustomAttribute<FeatureFlagActionAtrribute>();
    ActionDescriptor actionDescriptor = new ControllerActionDescriptor()
                {
                    ActionName = methodInfoDoSomethingWithFilterAction.Name,
                    ControllerName = typeof(FeatureFlagTest).Name,
                    DisplayName = methodInfoDoSomethingWithFilterAction.Name,
                    MethodInfo = methodInfoDoSomethingWithFilterAction,
                };

    ActionContext actionContext = new ActionContext(httpContext, routeData, actionDescriptor) ;
    var homeController = new HomeController();
    var attribute = new FeatureFlagActionAtrribute("user", new string[] { "feature1" });
    IDictionary<string, object> actionArguments = new Dictionary<string, object>
                {
                    ["user"] = "user1"
                };

    var filterMetadata = new List<IFilterMetadata>() { featureFlagActionAtrributeFilter };

    ActionExecutingContext actionExecutedContext = new 
    ActionExecutingContext(actionContext, filterMetadata, actionArguments, homeController);


    attribute.OnActionExecuting(actionExecutedContext);

Then inside the ActionFilterAttribute:

public override void OnActionExecuting(ActionExecutingContext context)
{
        ControllerActionDescriptor actionDescriptor = (ControllerActionDescriptor)context.ActionDescriptor;
        Debug.Print($"2. @Before Method called {actionDescriptor.ControllerName}Controller.{actionDescriptor.ActionName}");
        var controllerName = actionDescriptor.ControllerName;
        var actionName = actionDescriptor.ActionName;
        IDictionary<object, object> properties = actionDescriptor.Properties;
        ParameterInfo[] paramsOfMethod = actionDescriptor.MethodInfo.GetParameters();
        var fullName = actionDescriptor.DisplayName;

        var paramNameForKeyOfFeature = ParamNameForKeyOfFeature;

        var arguments = context.ActionArguments;
like image 1
Roland Roos Avatar answered Oct 13 '22 14:10

Roland Roos