C# NUnit
last modified July 5, 2023
C# NUnit tutorial shows how to do unit testing in C# with NUnit framework.
Unit testing is a software testing where individual units (components) of a software are tested. The purpose of unit testing is to validate that each unit of the software performs as designed. A unit is the smallest testable part of any software.
NUnit is a unit-testing library for all .NET languages. It was inspired by Java's JUnit. Other unit-testing libraries include XUnit and MSTest.
It is possible to place tests in the same project directory or inside a different directory. We start with a simpler option and place tests in the same project directory. In the end, we show the latter option.
$ dotnet add Microsoft.NET.Test.Sdk $ dotnet new nunit $ dotnet add NUnit3TestAdapter
In order to use NUnit, we need to add these three libraries.
C# NUNit simple example
We start with a simple example.
namespace Arithmetic; class Basic { public static Func<int, int, int> add = (a, b) => a + b; public static Func<int, int, int> mul = (a, b) => a * b; public static Func<int, int, int> sub = (a, b) => a - b; public static Func<int, int, int> div = (a, b) => a / b; }
We test simple arithmetic functions.
We put our tests into the tests directory. NUnit automatically discovers our tests.
namespace Testing; using NUnit.Framework; using Arithmetic; class ArithTest { [Test] public void SimpleArithmetic() { int r1 = Basic.add(3, 3); Assert.AreEqual(r1, 6); int r2 = Basic.sub(3, 3); Assert.AreEqual(r2, 0); int r3 = Basic.mul(3, 3); Assert.AreEqual(r3, 9); int r4 = Basic.div(3, 3); Assert.AreEqual(r4, 1); } }
The test method is annotated with the [Test]
attribute. We use
assertions to ensure the correct output.
$ dotnet test ... Starting test execution, please wait... A total of 1 test files matched the specified pattern. Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, ...
C# NUnit skipping tests
Test methods can be skipped with [Ignore]
attribute.
namespace Testing; using NUnit.Framework; using Arithmetic; class ArithTest { [Test] public void AddSub() { int r1 = Basic.add(3, 3); Assert.AreEqual(r1, 6); int r2 = Basic.sub(3, 3); Assert.AreEqual(r2, 0); } [Test] [Ignore("Ignoring")] public void MulDiv() { int r3 = Basic.mul(3, 3); Assert.AreEqual(r3, 9); int r4 = Basic.div(3, 3); Assert.AreEqual(r4, 1); } }
We have two test methods. One of them is skipped using the [Ignore]
attribute.
C# NUnit TestCase
With TestCase
attribute, we can have parameterized test methods.
namespace Testing; using NUnit.Framework; using Arithmetic; class ArithTest { [TestCase(1, 2, 3)] [TestCase(2, 2, 4)] [TestCase(-1, 4, 3)] public void Add(int x, int y, int z) { int r = Basic.add(x, y); Assert.AreEqual(r, z); } [TestCase(1, 2, -1)] [TestCase(2, 2, 0)] [TestCase(3, 2, 1)] public void Sub(int x, int y, int z) { int r = Basic.sub(x, y); Assert.AreEqual(r, z); } [TestCase(9, 3, 27)] [TestCase(3, 3, 9)] [TestCase(-3, -3, 9)] public void Mul(int x, int y, int z) { int r = Basic.mul(x, y); Assert.AreEqual(r, z); } [TestCase(9, 3, 3)] [TestCase(3, 3, 1)] [TestCase(8, 2, 4)] public void Div(int x, int y, int z) { int r = Basic.div(x, y); Assert.AreEqual(r, z); } }
In this example, we test each method with three sets of values.
C# NUnit TestCaseSource
The [TestCaseSource]
attribute allows us to read the data for the
parameterized test methods from different sources.
namespace Testing; using NUnit.Framework; using Arithmetic; public class ArithTest { [TestCaseSource(nameof(AddCases))] public void Add(int x, int y, int z) { int r = Basic.add(x, y); Assert.AreEqual(r, z); } [TestCaseSource(nameof(SubCases))] public void Sub(int x, int y, int z) { int r = Basic.sub(x, y); Assert.AreEqual(r, z); } [TestCaseSource(nameof(MulCases))] public void Mul(int x, int y, int z) { int r = Basic.mul(x, y); Assert.AreEqual(r, z); } [TestCaseSource(nameof(DivCases))] public void Div(int x, int y, int z) { int r = Basic.div(x, y); Assert.AreEqual(r, z); } static object[] AddCases = { new object[] { 1, 2, 3 }, new object[] { 2, 2, 4 }, new object[] { -1, 4, 3 } }; static object[] SubCases = { new object[] { 1, 2, -1 }, new object[] { 2, 2, 0 }, new object[] { 3, 2, 1 } }; static object[] MulCases = { new object[] { 9, 3, 27 }, new object[] { 3, 3, 9 }, new object[] { -3, -3, 9 } }; static object[] DivCases = { new object[] { 9, 3, 3 }, new object[] { 3, 3, 1 }, new object[] { 8, 2, 4 } }; }
In this example, the values are placed in arrays.
C# NUnit ExpectedResult
With ExpectedResult
, we can simplify our test setup.
namespace Testing; using NUnit.Framework; using Arithmetic; class ArithTest { [TestCase(1, 2, ExpectedResult = 3)] [TestCase(2, 2, ExpectedResult = 4)] [TestCase(-1, 4, ExpectedResult = 3)] public int Add(int x, int y) { return Basic.add(x, y); } [TestCase(1, 2, ExpectedResult = -1)] [TestCase(2, 2, ExpectedResult = 0)] [TestCase(3, 2, ExpectedResult = 1)] public int Sub(int x, int y) { return Basic.sub(x, y); } [TestCase(9, 3, ExpectedResult = 27)] [TestCase(3, 3, ExpectedResult = 9)] [TestCase(-3, -3, ExpectedResult = 9)] public int Mul(int x, int y) { return Basic.mul(x, y); } [TestCase(9, 3, ExpectedResult = 3)] [TestCase(3, 3, ExpectedResult = 1)] [TestCase(8, 2, ExpectedResult = 4)] public int Div(int x, int y) { return Basic.div(x, y); } }
With ExpectedResult
, our code is shortened a bit.
Placing tests in separate directory
In the following example, we show how to place tests in a separate directory.
$ mkdir Separate $ cd Separate
We create a new directory.
$ dotnet new sln
We create a new empty solution.
$ mkdir PalindromeService PalindromeService.Tests
Two directories are created.
$ cd PalindromeService $ dotnet new classlib
We create a new library.
namespace Palindrome.Services; using System.Globalization; public class PalindromeService { public bool IsPalindrome(string word) { IEnumerable<string> GraphemeClusters(string s) { var enumerator = StringInfo.GetTextElementEnumerator(s); while (enumerator.MoveNext()) { yield return (string)enumerator.Current; } } var reversed = string.Join("", GraphemeClusters(word).Reverse().ToArray()); return reversed == word; } }
The PalindromeService
contains the IsPalindrome
method, which determines if a words is a palindrome.
$ cd .. $ dotnet sln add PalindromeService\PalindromeService.csproj
We add the PalindromeService
to the solution.
$ cd PalindromeService.Tests $ dotnet add nunit $ dotnet add reference ..\PalindromeService\PalindromeService.csproj
We go to the PalindromeService.Tests
directory and add the
unit
libraries add the reference to the
PalindromeService
.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <Nullable>enable</Nullable> <IsPackable>false</IsPackable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="nunit" Version="3.13.3" /> <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" /> <PackageReference Include="coverlet.collector" Version="3.1.0" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\PalindromeService\PalindromeService.csproj" /> </ItemGroup> </Project>
This is how the project file looks like.
namespace Palindrome.Services.Tests; using NUnit.Framework; public class Tests { private PalindromeService? _palindromeService; [SetUp] public void SetUp() { _palindromeService = new PalindromeService(); } [TestCase("racecar")] [TestCase("nun")] [TestCase("level")] public void IsPalindrome(string word) { var r = _palindromeService!.IsPalindrome(word); Assert.AreEqual(r, true); } }
We test the IsPalindrome
method with three words.
[SetUp] public void SetUp() { _palindromeService = new PalindromeService(); }
The [Setup]
attribute is used to provide a common set of functions
that are performed just before each test method is called. In our case, we
create the PalindromeService
.
$ cd .. $ dotnet sln add PalindromeService.Tests\PalindromeService.Tests.csproj
We add the test project to the solution.
$ dotnet test
Finally, we can run the tests.
Source
Unit testing C# with NUnit and .NET Core
In this article we have done unit testing in C# with NUnit library.
Author
List all C# tutorials.