Future-proofing .NET Tests with NUnit Values Attributes

Matt Eland
4 min readNov 14, 2019

In this article, I’ll propose a C# solution to a common testing problem with enums using a special NUnit attribute. I’ll also introduce you to a related attribute which can expand your tests.

Recently I wrote some thoughts on code coverage. In that article, I discussed a method that switched over enum values to return a string and how the method will break if the enum expands. That article got me thinking about how we should be testing things like enums.

Let me walk you through my solution.

The Problem

To do a quick recap of my prior article, the problem is if you have a switch statement like the following:

In this case, if we ever add a new enum value, that enum will return “ow” by default. That might be fine for a game project, but imagine a scenario where you needed to display a user-facing string in a business application.

Even if we had the application throw a new error on the default case, there’s no way to conclusively ensure that the method actually gets tested during development and testing before being released to production.

Since I’m all about making it impossible for various types of defects to ever exist, let’s look at my current preferred solution for this.

NUnit Values to the Rescue

The NUnit testing framework is currently my preferred unit test library for C# development. XUnit is close behind, but I find NUnit’s attribute naming more intuitive and it offers some capabilities that XUnit does not.

One of those capabilities is the use of a Values attribute. This is an attribute that can be used to decorate a test method like so:

This code result in 3 different tests at time of execution — one that uses an input of 1, then another for 2, and finally one for 3. While I would tend to use TestCase attributes for this specific scenario, this example should tell you a little bit about the Values attribute in NUnit.

What’s really cool about Values is that you can apply it to enums. Take a look at this code:

This unit test will run once for every member defined in DamageType and pass that value in as input.

Put another way — if we ever add a new DamageType, the test will automatically gain a new test and verify its behavior. While this might not look like much, it helps ensure that unexpected errors are not occurring with new enum values.

I actually put this type of technique to use today in my day job on two large enums and was able to find a number of failing cases that we had no idea were even lurking deep in the code. Not only did adopting Value attributes help prevent future bugs, but it exposed current bugs we didn't even know we had!

But Wait, There’s More!

While I’m not sure why you’d ever want to do this, the Values attribute can be applied to boolean parameters, allowing NUnit to automatically inject both true and false values. Personally, I'd rather use explicit TestCase attributes for readability, but this is something you can do if you want to.

More practically, NUnit offers a Range attribute which works similarly to the Values attribute, but generates a range of possible values.

Let’s say you wanted to test some percentage math:

Here, the Range attribute lets us define an inclusive range from a low value to a high value. This will then pass in all integers between 0 and 100 to this test method as percentWhole on a unique unit test variant.

This specific example might be good for finding boundary issues at 0 or 100, or for exposing issues with various areas on rounding around 33% or 66%.

Conclusion

Hopefully I’ve convinced you that Values and potentially Range are worth investigating if you're doing .NET unit testing.

While I should caution you that if you have a large number of unit tests, you’re paying for the overhead of NUnit jumping from one test to another. In most projects and scales this won’t be an issue, but if you habitually use Range and Values attributes, you could get to some large sizes fairly quickly.

The range-based technique is now my preferred and standard way of testing enums going forward, because it helps mitigate the risk that I’ll forget to test something.

What other testing techniques have you investigated and enjoyed?

Originally published at https://killalldefects.com on November 14, 2019.

--

--

Matt Eland

Microsoft MVP in AI, AI Specialist at Leading EDJE. Author of "Refactoring with C#".