Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Test the BindAttribute for method parameters

I am looking to write unit tests to validate my controller while ensuring that the bind properties are setup correctly. With the following method structure, how can I ensure that only the valid fields are passed from a unit test?

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

Broader Explanation: Many of our models have lists of allowed values that we don't want to send back and forth, so we rebuild them when the (ModelState.IsValid == false). In order to ensure these all work, we want to put unit tests in place to assert that the list was rebuilt, but without clearing the list before calling the method, the test is invalid.

We are using the helper method from this SO answer for ensuring the model is validated, and then our unit test is something like this.

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

Any assistance or pointers in the right direction is greatly appreciated.

like image 308
Martin Noreke Avatar asked Jun 05 '15 16:06

Martin Noreke


People also ask

How to specify parameters for the same unit test method?

In C#, it is possible to specify parameters for the same unit test method. Example: [DataTestMethod] [DataRow (12,3,4)] [DataRow (12,2,6)] public void DivideTest (int n, int d, int q) { Assert.AreEqual ( q, n / d ); }

What are NUnit parameter attributes?

Sometimes the cardinality of the parameters of a unit test is such that creating a test case for each combination of valid values would be a tedious job. Luckily, NUnit comes with a set of parameter attributes that tell the test runner to generate a test for each value.

Is there a way to parameterize tests in JUnit?

JUnit has parameterized tests The only drawback is that all test methods in the class will be executed for each parameter (row). Thanks Timothy, but as I said, I don't like this solution. The reason is parameters and testing methods are too distinct. That is why I think it is not as easy to use than C# solution.

What are the advantages of multiple inputs in unit testing?

These tests are convenient because they give the possibility to execute the same test against different set of parameters. A typical example is validating email addresses: by specifying multiple inputs, you can ensure the validation logic is tested against all corner cases without the need of rewriting the full unit test.


1 Answers

I may be misinterpreting what you're saying, but it sounds like you want something to ensure that a model you've created in your test is filtered before it is passed to your controller in order to simulate MVC binding and to prevent you accidentally writing a test that passes information to your controller under test that would never actually be populated by the framework.

With this in mind, I've assumed you're only really interested in Bind attributes with the Include member set. In which case you could use something like this:

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

Which as it stands would be called from your unit test, before you invoke the controller action like this:

controllerToTest.PreBindModel(model, "SomeMethod");
var result = controllerToTest.SomeMethod(model);

Essentially, what it does is iterate through each of the parameters that are being passed to a given controller method, looking for bind attributes. If it finds a bind attribute, then it gets the Include list, then it resets every property of the viewModel that isn't mentioned in the include list (essentially unbinding it).

The above code may need some tweaking, I don't do much MVC work, so I've made some assumptions about the usage of the attribute and models.

An improved version of the above code, that uses the BindAttribute itself to do the filtering:

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);
            }
        }
    }
}
like image 163
forsvarir Avatar answered Sep 28 '22 01:09

forsvarir