Writing parameterized unit tests in C# — Run the same test with various inputs

Photo by Caspar Camille Rubin on Unsplash

Unit testing is often (unfortunately) an afterthought or something done in haste. Therefore, it’s important to get as much tested with as little code/effort as possible. One of the best ways to do that is to write a single test which will support multiple test cases through parameters. This also allows the tests that we write to be more robust as we can run through more inputs than we would otherwise.

Say, for example, we want to implement a unit test for a basic FizzBuzz. For this example I’m using the following specification:

Write a function that accepts a single int as input. For multiples of ‘3’ print “Fizz”, for the multiples of ‘5’ print “Buzz”, and for any other number, simply print the number itself.

Now, we’re going to write a unit test for this but we’re going to parameterize it. For these examples we’re going to use MSTest (Supported since MSTest V2 June 17, 2016) but the same functionality is supported in other frameworks like XUnit and NUnit. In C# with MSTest it would look something like this:

[TestMethod]
[DataRow(1)]
[DataRow(2)]
[DataRow(4)]
[DataRow(7)]
[DataRow(8)]
[DataRow(11)]
public void FizzBuzz_Numeric(int input)
{
Assert.AreEqual(input.ToString(), UnderTest.FizzBuzz(input));
}
[TestMethod]
[DataRow(3)]
[DataRow(6)]
[DataRow(9)]
[DataRow(12)]
public void FizzBuzz_Fizz(int input)
{
Assert.AreEqual("Fizz", UnderTest.FizzBuzz(input));
}
[TestMethod]
[DataRow(5)]
[DataRow(10)]
[DataRow(20)]
public void FizzBuzz_Buzz(int input)
{
Assert.AreEqual("Buzz", UnderTest.FizzBuzz(input));
}
[TestMethod]
[DataRow(15)]
[DataRow(30)]
[DataRow(45)]
public void FizzBuzz_FizzBuzz(int input)
{
Assert.AreEqual("FizzBuzz", UnderTest.FizzBuzz(input));
}

Each of these take a decent representation of the inputs and make sure they match the expected outputs.

Test result display in Visual Studio 2022 via Test Explorer pane

Another way of doing this is to write fewer tests but to also pass in the expected value, like so:

[TestMethod]
[DataRow(1, "1")]
[DataRow(2, "2")]
[DataRow(3, "Fizz", DisplayName = "Multiple of 3: 3 ")]
[DataRow(4, "4")]
[DataRow(5, "Buzz", DisplayName = "Multiple of 5: 5 ")]
[DataRow(6, "Fizz", DisplayName = "Multiple of 3: 3 ")]
[DataRow(10, "Buzz", DisplayName = "Multiple of 5: 10 ")]
[DataRow(11, "11")]
[DataRow(15, "FizzBuzz", DisplayName = "Multiple of 3 & 5: 15 ")]
public void FizzBuzz(int input, string expected)
{
Assert.AreEqual(expected, UnderTest.FizzBuzz(input));
}
Test result display in Visual Studio 2022 via Test Explorer pane

Note that these test results include a separate line for each set of inputs. I’ve also added friendly display names to several of them that are reflected in the test explorer. (Note: Be careful if you provide display names not use duplicate the names! It won’t display them separately!)

Alternatives and trade-offs

There are other ways of doing this. Most of which have a big downside, or may be viable depending on your use-case.

The most obvious way is to write the values into the function itself. This would work. However, there’s a slight readability difference since in the example above, each [DataRow(...)] is quite obviously a unique test case. The bigger advantage to the parameterized test, is that Visual Studio will treat each [DataRow(...)] as technically a unique test. You can see in the image above, each one is displayed and pass/fail separately. The parameterized test method written technically counts as a unique test for each set of inputs.

Another way is to make a new test for each input. However, as you can see by the sheer number of scenarios I’m testing above with trivial effort you can see that it would be a lot of additional boilerplate with no advantage. In fact there’s a severe disadvantage to doing so. In the parameterized test you’ve written the test logic once. If it ever has to change, you only have to correct it once. If you copy/paste a bunch of different test methods you then have to maintain all those distinct test methods. DRY! Don’t Repeat Yourself!

Equivalency in XUnit or NUnit

The same test in XUnit would look like:

[Theory]
[InlineData(1, "1")]
[InlineData(2, "2")]
[InlineData(3, "Fizz")]
// Further cases omitted for brevity
public void FizzBuzz(int input, string expected)
{
Assert.AreEqual(expected, UnderTest.FizzBuzz(input));
}

The same test in NUnit would look like:

[Test]
[TestCase(1, "1")]
[TestCase(2, "2")]
[TestCase(3, "Fizz")]
// Further cases omitted for brevity
public void FizzBuzz(int input, string expected)
{
Assert.AreEqual(expected, UnderTest.FizzBuzz(input));
}

--

--

--

Full-stack software engineer/architect.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

WordPress custom Dashboard Login Page.

SuccessFactors 2H 2021 release updates for Analytics & Learning

Batch process/Schedule/Cron Job for .Net Developer

Notes On James Gosling Oral History

Monitoring/Observability

Anatomy of a Custom Rails Route

Understanding XML

Add Dll Plugin To Ableton Mac

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Aaron Moore

Aaron Moore

Full-stack software engineer/architect.

More from Medium

This is a blog post for my video https://www.youtube.com/watch?v=XEHR-AVou4U

Unable to find an OpenAPI description error in .net 6 REPL CLI tool

Display 3D Model using Window Presentation Foundation

[Dot Net Core](Graphic series )3.How to actually implement DI Resolve Service