单元测试方法参数的BindAttribute

Mar*_*eke 7 c# asp.net-mvc unit-testing model-binding

我期待编写单元测试来验证我的控制器,同时确保正确设置绑定属性.使用以下方法结构,如何确保仅从单元测试传递有效字段?

public ActionResult AddItem([Bind(Include = "ID, Name, Foo, Bar")] ItemViewModel itemData)
{
    if (ModelState.IsValid)
    {
        // Save and redirect
    }

    // Set Error Messages
    // Rebuild object drop downs, etc.
    itemData.AllowedFooValues = new List<Foo>();
    return View(itemData);
}
Run Code Online (Sandbox Code Playgroud)

更广泛的解释:我们的许多模型都有允许值的列表,我们不希望来回发送,所以我们在(ModelState.IsValid == false)时重建它们.为了确保这些工作,我们希望放置单元测试以断言列表已重建,但在调用方法之前没有清除列表,测试无效.

我们正在使用这个SO答案中的辅助方法来确保模型被验证,然后我们的单元测试是这样的.

    public void MyTest()
    {
        MyController controller = new MyController();

        ActionResult result = controller.AddItem();
        Assert.IsNotNull(result);
        ViewResult viewResult = result as ViewResult;
        Assert.IsNotNull(viewResult);
        ItemViewModel itemData = viewResult.Model as ItemViewModel;
        Assert.IsNotNull(recipe);
        // Validate model, will fail due to null name
        controller.ValidateViewModel<ItemViewModel, MyController>(itemData);

        // Call controller action
        result = controller.AddItem(itemData);
        Assert.IsNotNull(result);
        viewResult = result as ViewResult;
        Assert.IsNotNull(viewResult);
        itemData = viewResult.Model as ItemViewModel;
        // Ensure list was rebuilt
        Assert.IsNotNull(itemData.AllowedFooValues);
    }
Run Code Online (Sandbox Code Playgroud)

非常感谢任何正确方向的帮助或指示.

for*_*rir 3

我可能会误解你的意思,但听起来你想要一些东西来确保你在测试中创建的模型在传递到控制器之前经过过滤,以模拟 MVC 绑定并防止你意外编写将信息传递到被测控制器的测试,而框架实际上永远不会填充该信息。

考虑到这一点,我假设您只对将属性与Include成员集绑定真正感兴趣。在这种情况下你可以使用这样的东西:

public static void PreBindModel<TViewModel, TController>(this TController controller, 
                                                         TViewModel viewModel, 
                                                         string operationName) {
    foreach (var paramToAction in typeof(TController).GetMethod(operationName).GetParameters()) {
        foreach (var bindAttribute in paramToAction.CustomAttributes.Where(x => x.AttributeType == typeof(BindAttribute))) {
            string properties;
            try {
                properties = bindAttribute.NamedArguments.Where(x => x.MemberName == "Include").First().TypedValue.Value.ToString();
            }
            catch (InvalidOperationException) {
                continue;
            }
            var propertyNames = properties.Split(',');

            var propertiesToReset = typeof(TViewModel).GetProperties().Where(x => propertyNames.Contains(x.Name) == false);

            foreach (var propertyToReset in propertiesToReset) {
                propertyToReset.SetValue(viewModel, null);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在您调用控制器操作之前,它会从您的单元测试中调用,如下所示:

controllerToTest.PreBindModel(model, "SomeMethod");
var result = controllerToTest.SomeMethod(model);
Run Code Online (Sandbox Code Playgroud)

本质上,它的作用是迭代传递给给定控制器方法的每个参数,查找绑定属性。如果它找到绑定属性,则它会获取Include列表,然后重置viewModel包含列表中未提及的每个属性(本质上是解除绑定)。

上面的代码可能需要一些调整,我没有做太多 MVC 工作,所以我对属性和模型的使用做了一些假设。

上述代码的改进版本,使用 BindAttribute 本身来进行过滤:

public static void PreBindModel<TViewModel, TController>(this TController controller, TViewModel viewModel, string operationName) {
    foreach (var paramToAction in typeof(TController).GetMethod(operationName).GetParameters()) {
        foreach (BindAttribute bindAttribute in paramToAction.GetCustomAttributes(true)) {//.Where(x => x.AttributeType == typeof(BindAttribute))) {
            var propertiesToReset = typeof(TViewModel).GetProperties().Where(x => bindAttribute.IsPropertyAllowed(x.Name) == false);

            foreach (var propertyToReset in propertiesToReset) {
                propertyToReset.SetValue(viewModel, null);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)