Help your Colleagues Save Time by Using NUnit Attributes in your Tests

Use NUnit Test Attributes to Generate Method Input

Sami Islam
6 min readAug 24, 2020
Photo by Nathan Dumlao on Unsplash

Let’s face it, there is nothing worse than trying to understand the code you wrote 3 months ago. Well, to be honest, trying to understand the test code you wrote 3 months ago is worse.

And the worst of the worst - is when your colleagues try to understand the test code you wrote 3 months ago.

I really feel awkward when this happens, especially because I respect their time - which is the most expensive thing that any of us own.

In this article, I am going to take you through 5 ways of using the NUnit Test Framework to simplify your test code and make it more readable. I am going to focus only on the input to your Unit Tests and not the test itself or the correct way to name your tests.

A Simple Test to Begin With

A simple NUnit test method
A simple NUnit test method

The Class1 implements a TrueOnEvenNumbers check. You can see the repetition with multiple Asserts which is completely unnecessary. It’s worse when something like this is done in a loop inside the test method.

Having a single assertion per test makes it easier to understand and maintain the test code. Let’s use the NUnit TestCase attribute to do just that.

1. The TestCase Attribute

Using the NUnit TestCase method-level attribute
Using the NUnit TestCase method-level attribute

NUnit is going to run the test once per theTestCase attribute. The refactored test method now takes an input and returns an output. The input is taken from the first parameter of the TestCase attribute. The output is checked against the ExpectedResult property of the TestCase attribute. I love this since the test now focuses more on the logic being tested and has a single assertion.

The NUnit Test output:

Output of the test using the TestCase attribute
Output of the test using the TestCase attribute

The output shows the exact parameter value being passed in each time the test method is called.

The test method logic used here are:

  1. Even numbers return true
  2. Numbers that are not even numbers return false

If you think it makes more sense to separate out the logic, it can be done nicely by using the Range attribute from NUnit.

2. The Range Attribute

Using the NUnit Range parameter-level attribute
Using the NUnit Range parameter-level attribute

Separating the test logic, I now test the result of even number inputs. A great way to do that is to use the Range attribute that takes a ‘from’, a ‘to’ and a ‘step’ value.

In this example I want the input to be 40, 42, 44 and 46 and therefore I use a Range(40, 46, 2) that starts ‘from’ 40, go ‘to’ 46, and increments at each ‘step’ by 2. Notice how the Range attribute is used on the input parameter and not on the test method.

The output:

Output of the test using the Range attribute
Output of the test using the Range attribute

And the test for odd numbers would look like the following:

Another test using the Range attribute
Another test using the Range attribute

Wait, what is that Should().BeFalse() syntax?!

Well, that is another way to make your tests more readable. The wonderful FluentAssertions Extensions allow for such fluent syntax during assertion: https://fluentassertions.com/introduction

3. The TestCaseSource Attribute

The TestCase attribute that we used allows us to also use Enumerations:

Using enumeration in the TestCase attribute
Using enumeration in the TestCase attribute

For a given workday, test that the mood one is in matches the expected value. Notice the return type of the GetMoodWithReasonForWorkday method:

The method - GetMoodWithReasonForWorkday
The method - GetMoodWithReasonForWorkday

It is not the enumeration Mood but the type MoodWithReason :

The struct - MoodWithReason
The struct - MoodWithReason

I would like to test both the properties of the struct MoodWithReason and would like to use it as the ExpectedResult property in the TestCase attribute like this:

Using MoodWithReason as the TestCase ExpectedResult type
Using MoodWithReason as the TestCase ExpectedResult type

But that leads to the following compilation error:

Compilation error when trying to use a struct as the ExpectedResult type
Compilation error when trying to use a struct as the ExpectedResult type

Well have no fear, the TestCaseSource attribute is here:

Using the NUnit TestCaseSource method-level attribute
Using the NUnit TestCaseSource method-level attribute

The TestCaseSource attribute allows us to define any number and type of input (including expected results) and use it as input to our test methods. The iterator MoodWithReasons (with an ‘s’ at the end) is defined as followed:

Iterator MoodWithReasons returning TestCaseData
Iterator MoodWithReasons returning TestCaseData

We can yield any number of TestCaseData items from the iterator. The TestCaseData type can take any number of parameters. We return two values for the two input parameters of our test method (Workday workday, MoodWithReason moodWithReason) . Unfortunately, the output of running the tests with our generated parameters look like this:

Output of using MoodWithReasons TestCaseSource
Output of using MoodWithReasons TestCaseSource

The second parameter is of type MoodWithReason and does not show the actual values used during the test. To see the actual parameters passed in, a different TestCaseSource can be used:

Iterator SeparatedMoodWithReasons returning TestCaseData
Iterator SeparatedMoodWithReasons returning TestCaseData

The corresponding Test Method now takes three input parameters:

New method using TestCaseSource SeparatedMoodWithReasons
New method using TestCaseSource SeparatedMoodWithReasons

And the corresponding Test Output shows all input parameter values:

Output of using TestCaseSource SeparatedMoodWithReasons
Output of using TestCaseSource SeparatedMoodWithReasons

4. The Values Attribute

The Values attribute creates a combination of each possible input value. Let’s see what that means:

Using the NUnit Values parameter-level attribute
Using the NUnit Values parameter-level attribute

Here the Values attributes are used on the parameters of type Mood and Reason, each being an enumeration:

The enumerations Mood and Reason
The enumerations Mood and Reason

Running the test lead to a total of 10 test method calls:

Output of the automatic combination of the Mood and Reason values
Output of the automatic combination of the Mood and Reason values

As you can see NUnit generates a test for each possible combination of the two input parameter types!! How cool is that?

There are a few Test Method level attributes that can be used together with the Values attribute for specific purposes. A list of all the NUnit attributes can be found at:

https://docs.nunit.org/articles/nunit/writing-tests/attributes.html

5. The ValueSource Attribute

Just like the TestCaseSource attribute, there is a ValueSource attribute.

Using the NUnit Values parameter-level attribute again
Using the NUnit Values parameter-level attribute again

Using the Values attribute you could write a test to check that any multiplication of the firstValue and the secondValue always gives a value of less than 10000. Another way to do that would be to use the ValueSource attribute:

Using the NUnit ValueSource parameter-level attribute
Using the NUnit ValueSource parameter-level attribute

And the parameter sources for the test, FirstValueSource and SecondValueSource, can be defined as:

The Iterators FirstValueSource and SecondValueSource
The Iterators FirstValueSource and SecondValueSource

And the corresponding Test Method output is as follows:

Output of using the ValueSource parameter-level attribute
Output of using the ValueSource parameter-level attribute

There are a lot of other useful NUnit attributes for varying purposes. I have found the ones I described to be the most helpful.

All code used in this article can be found in my GitHub repository.

I hope that my article helps others.

--

--