We all know that testing is essential, but how can we make sure our tests are clean and work better for us? What do we do when we have to test the results of different input values for a single method? .NET test frameworks provide a consistent answer to this challenge in the form of parameterized tests. Here, we’ll see how xUnit provides parameterized tests with its Theory and InlineData attributes.
TL;DR Using xUnit’s built in Theory and InlineData attributes will help keep your tests clean and refactorable. This will save you time and frustration.
Often our system under test can expect a variety of different values that produce specific results. We need to write tests that ensure the correct results are produced by those different values.
An example is a method that does simple addition. Many different operands/parameters can be added together with different expected results. We would ideally have multiple tests for the addition method to ensure it works as designed. Depending on the number of different values we want to test, writing separate tests for each can become tedious and unwieldy at the very least.
Let’s create a simple but more interesting example. Everyone has a credit score. According to Experian, a Credit Score ranges from 300 to 850. Depending on the value of your score, your Credit Rating will be anywhere from “Very Poor” to “Excellent”.
In our scenario, we have been tasked to create a class that, when provided with a credit score, will determine the Credit Rating based on that score. Other requirements include setting the rating to “Invalid” if the provided score is outside the preset range.
We will call our class CreditRating and it will take an int parameter in the constructor that represents the credit score, and our class will have a property called Rating that is set based on the provided score.
In order to fully test this class, we decide to test with a variety of different credit scores and assert the Rating is correct. We have picked several numbers in each Rating range as well as numbers below 300 and above 850. That’s a fair number of tests.
Here’s what our test class looks like after writing two tests:
We can see that writing a separate test to cover all our test cases will quickly become tedious and unmanageable. Not to mention, it violates at least one principle we’ve all had drilled into our little programmer brains: Don’t Repeat Yourself (DRY). Think about how you will have to change each and every test if a new requirement is given. What if the scale changes or new classes or type dependencies are introduced? It happens, and we need to program to handle those changes with as little grief and disruption as possible.
xUnit’s Theory attribute helps us keep our tests clean and safe against requirement changes and refactoring. Check out our CreditRatingTests class now.
That looks much better and more maintainable. The test is succinct and future proof. The attributes allow us to test the upper and lower bounds of each credit rating range without duplicating code in multiple tests. If the credit rating scale changes, we only need to update the InlineData attributes, not change multiple tests. If we introduce new or different constructor arguments, we only need to update the construction of CreditRating in one place.
As a bonus, Visual Studio Test Runner groups the tests as if they were individual tests, which makes it quick and easy to understand which data causes the CreditRating class to fail.
Using xUnit’s Theory and InlineData attributes helps us to keep our tests clean and saves us time by allowing easy refactoring and adding additional test parameters in one test. Following this pattern prevents monotonous test writing and headaches in the future.