How to unit test ActionFilterAttribute
Asked Answered
F

2

20

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);
            }
        }
    }
Fescennine answered 11/9, 2017 at 19:17 Comment(0)
C
46

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>();
    }
} 
Cheerly answered 11/9, 2017 at 19:29 Comment(9)
Thanks for the response. Found this after accepting that helped as well: #36629891Fescennine
@nkosi Quick q: when I try the above code, the ModelState is VALID. Is there a trick to get the modelState to be in an invalid state? Do we need to create some fake route or something?Inculpable
@Inculpable you would need to add a model error to the model state dictionary to make it invalid.Cheerly
I'm only getting "null" back , any suggestions why?Tereus
@FrankR.Haugen which part are you getting nullCheerly
@Cheerly context.Result.Should().NotBeNull() is always falseTereus
@FrankR.Haugen If you have a condition that depends on model state then you would need to add a model error to the model state dictionary so that is is invalid and would enter your conditional statementCheerly
@Cheerly I added this before I saw your answer-comment: Link to question (So you know my code)Tereus
@Nkosi, Thanks, your posts always seems to find a way to help meErbil
C
1

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;
Culinary answered 15/4, 2020 at 7:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.