Как выполнить модульное тестирование ActionFilterAttribute

Я хочу протестировать ActionFilterAttribute в проекте API .NET Core 2.0 и задаюсь вопросом, как лучше всего это сделать. Обратите внимание: я не пытаюсь проверить это с помощью действия контроллера, а просто проверяю сам ActionFilterAttribute.

Как я могу проверить это:

    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (!context.ModelState.IsValid)
            {
                context.Result = new BadRequestObjectResult(context.ModelState);
            }
        }
    }

person ovation22    schedule 11.09.2017    source источник


Ответы (2)


Создайте экземпляр контекста, передайте его фильтру и подтвердите ожидаемое поведение.

Например

[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>();
    }
} 
person Nkosi    schedule 11.09.2017
comment
Спасибо за ответ. Нашел это после принятия, что также помогло: .com/questions/36629391/ - person ovation22; 11.09.2017
comment
@nkosi Быстрый вопрос: когда я пробую приведенный выше код, ModelState является ДЕЙСТВИТЕЛЬНЫМ. Есть ли способ заставить modelState находиться в недопустимом состоянии? Нам нужно создать какой-то поддельный маршрут или что-то в этом роде? - person Pure.Krome; 17.03.2018
comment
@Pure.Krome, вам нужно будет добавить ошибку модели в словарь состояния модели, чтобы сделать ее недействительной. - person Nkosi; 19.03.2018
comment
Я получаю только null назад, какие-либо предложения, почему? - person Frank R. Haugen; 19.03.2020
comment
@FrankR.Haugen, какую часть вы получаете нулевой - person Nkosi; 19.03.2020
comment
@Nkosi context.Result.Should().NotBeNull() всегда ложно - person Frank R. Haugen; 19.03.2020
comment
@FrankR.Haugen Если у вас есть условие, которое зависит от состояния модели, вам нужно будет добавить ошибку модели в словарь состояния модели, чтобы она была недействительной и ввела бы ваш условный оператор. - person Nkosi; 19.03.2020
comment
@Nkosi Я добавил это до того, как увидел ваш ответ-комментарий: Ссылка на вопрос (чтобы вы знали мой код) - person Frank R. Haugen; 19.03.2020
comment
@Nkosi, спасибо, кажется, твои посты всегда находят способ помочь мне. - person johnny 5; 24.04.2020

Вот пример из реальной жизни, где я также получаю доступ к информации и параметрам метода внутри атрибута фильтра действия:

Предположим, у меня есть метод контроллера с ActionAttribute следующим образом:

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

HTTP-вызов будет примерно таким:

/Home/DoSomethingWithFilterAction?user="user1"

Теперь я хочу протестировать ActionAttribute FeatureFlagActionAtrribute в таком контексте.

Если вы примените приведенное выше предложение к этому примеру, это будет выглядеть так (по крайней мере, у меня сработало)

 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);

Затем внутри 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;
person Roland Roos    schedule 15.04.2020