Improve your parameterized Tests with named ValueTuples

TL;DR: For parameterized tests, declare your samples as value tuple array and subsequently Select it to IEnumerable<object[]>, to be type-safe, concise and descriptive.

Parameterized tests are great. They can make unit tests much less verbose and incentivize developers to write comprehensive tests for valid and invalid method arguments all over their respective co-domain. All major unit testing frameworks (for C# xUnit.net, NUnit and MSTest 2) support them in two flavors:

  • attribute-based, where you declare the test sample data in attributes right above test method
  • datasource-based, where you only declare the source of the test sample data in a test method attribute and then provide the actual data in plain code elsewhere, often via some public static field, property or method in the test class.

Because the first approach suffers from severe limitations C# puts on expressions that can be used in Attributes, I employ the second method most of the time.

Whether you are using xUnit’s [MemberData], NUnit’s [TestCaseSource] or MSTest’s [DynamicData], the return value of your data source of choice must be IEnumerable<object[]> for the testing framework to be able to recognize and use it correctly. The IEnumerable part is fine as it enables you to lazily yield the test samples. which can be useful sometimes.

But actually declaring the samples as object[] has some disadvantages:

  • you lose type-safety
  • you won’t even notice a wrong number of arguments / array elements at compile time
  • you need to wrap everything in an object array, even single argument samples

A test sample for a fictitious test of, say integer parsing, would be declared like this:

IEnumerable<object[]> MySamples = new[]
{
    new object[] { "1", 1 },
    new object[] { "-1", -1 },
    new object[] { "123", 123 }
};

which somehow manages to be simultaneously verbose (look at all the new object[]) and nondescript. It will also not catch mistakes like accidentally writing new object[] { 2, 2 } or even new object[] { "2", 2, 2 } at compile time.

By using named value tuples (available since C# 7) we can all at once be type-safe, concise and self-documenting in our sample declarations:

IEnumerable<object[]> MySamples = new (string StringToParse, int ExpectedParsedInt)[]
{
    ("1", 1),
    ("-1", -1),
    ("123", 123)
}.Select(t => new object[] { t.StringToParse, t.ExpectedParsedInt });

The “value tuple” part gives us type safety and conciseness, the “named” part gives us self-documentation. All we need to do is to Select the tuple array into the IEnumerable<object[]> expected by the test framework.

Named value tuples have become the way for me to declare my test samples.

In a future blog post, I will turn this up to eleven by using custom collection initializers to enable type-safe generic test samples.