Using DataTestMethod and DataRow to pass parameters to a unit test and prevent duplicating test methods

Microsoft .NET

In TDD, you will need to test for various inputs based on some condition. For example, we have a class that is responsible for generating dynamic SQL for a reporting system that allows the user to select from various operators. They might choose < for a integer field, or > for a money field, but we want to ensure they are not able to select these operators for a text field, where those comparisons don’t make sense.

For this scenario, would typically write a unit test for each combination of operator and field type to cover all cases. Instead, you should use DataTestMethod and DataRow, which reuses the same unit test method by passing in the operator and field type as parameters.

[DataTestMethod]
[DataRow("<", ReportFieldTypes.Boolean)]
[DataRow("<=", ReportFieldTypes.Boolean)]
[DataRow(">", ReportFieldTypes.Boolean)]
[DataRow(">=", ReportFieldTypes.Boolean)]
[DataRow("<", ReportFieldTypes.Text)]
[DataRow("<=", ReportFieldTypes.Text)]
[DataRow(">", ReportFieldTypes.Text)]
[DataRow(">=", ReportFieldTypes.Text)]
public void SetFilters_HasSearchFilterWithPrimitiveOperatorForNonPrimitiveField_ThrowsException(string @operator, string fieldType)
{
    mockTemplate.Setup(p => p.CreateDataSource()).Returns(new SqlDataSource(mockContext.Object, "view"));
    mockTemplate.Setup(p => p.Fields).Returns(new[]
                                                {
                                                    new ReportTemplateField("Field1")
                                                    {
                                                        Type = fieldType
                                                    }
                                                });

    report = new Report(product, mockTemplate.Object, user)
                {
                    Name = "Test"
                };

    Assert.ThrowsException<ArgumentException>(() => report.SetFilters(new List<ReportFieldFilter>
                                                                        {
                                                                            new SearchFilter
                                                                            {
                                                                                Field = "Field1",
                                                                                Operator = @operator,
                                                                                Value = "A"
                                                                            }
                                                                        }));
}

These attributes are provided by MSTest, so they are naturally supported by the MSTest runner in Visual Studio, and by ReSharper. Note also the method parameters @operator and fieldType, which will hold the values of the first and second parameters of the DataRow attributes. Each test runner will run a new unit test for each DataRow. Here is a screenshot from ReSharper’s test runner.

Overall this has reduced our test code quite a bit. We have a lot of tests in our business layer library for validating many inputs on various methods, and using this you simply add new attributes as new cases come up. It’s not intended to help you write living breathing dynamic unit test methods for multiple types of scenarios with common code, but when the test is the same, just with various inputs, this is a great alternative to duplicating test methods.

As with any test framework, and in TDD, your test code should be maintained just like everything else, including reusing as much code as possible. We have found that we typically have 3x the amount of test code as code under test, sometimes ranging upward of 4x-5x for some of our integration test classes.

Leave a Comment

Your email address will not be published.