Quick Overview:
A practical approach is followed in the blog to help you understand setting up and writing unit tests in .NET Core. MSTest framework is utilized to demonstrate the process along with their expected output. By following the curated content, you can gain detailed insight into the .NET core unit test scenarios that can help you with real-world projects.
Writing Unit Tests in .NET Core
In a software development lifecycle, not only testers but also .NET developers validate the application’s working. The development team conducts .NET core unit tests, which help them remove bugs and forward a high-performing, issue-free, and secure application.
The entire procedure for unit testing is quite streamlined. Here, we have provided the complete practical process to help you with writing .NET unit test cases according to your requirements and convenience.
.NET Core Unit Test: What and Why?
Whenever a .NET Core application is developed, it goes under numerous testing procedures. But, before handing the app to testers, .NET developers test the application on their end, and it’s known as .NET Core unit tests.
In unit testing, multiple test cases are created, and a test framework is utilized. The main aim of developer testing the software is to find and resolve the bugs, issues, and exceptions. In addition, it also aids in reducing the tester’s workload and releasing the app as per the defined schedule.
Moreover, unit testing benefits with:
- Improving code quality.
- Early-stage bug detection.
- Enhancing code reusability and accelerating deployment.
Frameworks Utilized For .NET Core Unit Test Purposes
In .NET Core, the following three frameworks are mostly utilized by a .NET development company to verify the functioning of the application.
- MSTest
- NUnit
- xUnit
All these frameworks are used for a similar purpose, that is, to write test cases. The only difference between the frameworks is their syntax, features, and core functionalities. However, when it comes to MSTest, no other technology can match it in terms of testing the .NET core application.
MSTest is the official framework from Microsoft that is, by default, integrated as a core feature of Visual Studio IDE. It’s considered as a high-end testing framework that works seamlessly with .NET CLI and numerous CI pipelines. In addition, it supports all .NET targets, including .NET Core, UWP, WinUI, .NET Framework, and more.
On the other hand, NUnit and xUnit are also considered reliable testing frameworks. However, they are only preferred if additional testing is required. Otherwise, MSTest is enough for .NET core unit tests.
How To Unit Tests in .NET Core: A Practical Approach
Here, we are going to use the MSTest framework to test the .NET Core application. To start with the practical tutorial, you need Visual Studio, which will help you create an Asp.net web app. Also, we are going to name the project “UnitTestDemo” for this tutorial.
Once your project is created, follow the below steps.
Step 1: Go under the solution explorer section and generate a new service class with the name “MessageService.cs”.
After the service class creation, add the below code to return a message using the string data type.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace UnitTestDemo.Services
{
public class MessageService
{
public Func<string> msg1 = () => "Hello there!";
public Func<string> msg2 = () => "Good Morning!";
}
}
Step 2: Add the MSTest library to the project. To do this, follow the below path.
Solution -> Add -> Add new project -> Select project -> MSTest Test project.
Check the solutions explorer section to ensure that MSTest is added. The output will look similar to the following snippet.
Step 3: Add the “UnitTestDemo” project reference to “TestDemo” to use the methods defined in the “MessageService.cs” file.
In addition, add a test class to the “MessageService.cs” file. Copy the following code to the file. There are two test methods that will validate the “MessageService.cs” file methods. In addition, “[TestClass]” is used for annotating class, and “[TestMethod]” is used to annotate test methods.
Moreover, an assert method is also included to help you detect errors and resolve them in the production environment.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitTestDemo.Services;
namespace TestDemo
{
[TestClass]
public class MessageTest
{
private const string Expected1 = "Hello there! Test!!!";
private const string Expected2 = "Good Morning!";
MessageService message = new MessageService();
[TestMethod]
public void Message1()
{
var m1 = message.msg1();
Assert.AreEqual(Expected1, m1);
}
[TestMethod]
public void Message2()
{
var m2 = message.msg2();
Assert.AreEqual(Expected2, m2);
}
}
}
Step 4: Now, run the tests to see how MSTest works.
In the solution explorer, you’ll find “MessageTest.cs” under the TestDemo. You have to right-click on it and hit the “Run Tests” option.
Step 5: Following the “Run Tests” click, the test methods will initiate their execution to verify the working of service methods. You will see the entire output on your screen, just like the snippet below.
As you can see above, a total of two tests were conducted, and the application passed them both. Further, if you modify the code, the output will also change accordingly.
We have completed the fundamentals of testing with MSTest. But now, we are going to modify some conditions and view the results.
Scenario #1: Parameterized Test
1st Step: Create a new service file (“ArithService.cs) under “UnitTestDemo” -> Services.
2nd Step: Add the code to the “ArithService.cs” file.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace UnitTestDemo.Services
{
public class ArithService
{
public Func<int, int, int> add = (a, b) => a + b;
public Func<int, int, int> mul = (a, b) => a * b;
public Func<int, int, int> sub = (a, b) => a - b;
public Func<int, int, int> div = (a, b) => a / b;
}
}
3rd Step: Generate a new test case with the name “MessageTest.cs”. Further, add the following code to the “ArithTest.cs” file. The code includes the test methods for validating the functionalities.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitTestDemo.Services;
namespace TestDemo
{
[TestClass]
public class ArithTest
{
ArithService arith = new ArithService();
[DataTestMethod]
[DataRow(1, 2, 3)]
[DataRow(2, 2, 4)]
[DataRow(-1, 4, 3)]
public void Add(int x, int y, int expected)
{
int r = arith.add(x, y);
Assert.AreEqual(r, expected);
}
[DataTestMethod]
[DataRow(1, 2, -1)]
[DataRow(2, 2, 0)]
[DataRow(3, 2, 1)]
public void Sub(int x, int y, int expected)
{
int r = arith.sub(x, y);
Assert.AreEqual(r, expected);
}
[DataTestMethod]
[DataRow(9, 3, 27)]
[DataRow(3, 3, 9)]
[DataRow(-3, -3, 9)]
[Ignore]
public void Mul(int x, int y, int expected)
{
int r = arith.mul(x, y);
Assert.AreEqual(r, expected);
}
[DataTestMethod]
[DataRow(9, 3, 3)]
[DataRow(3, 3, 1)]
[DataRow(8, 2, 4)]
[Ignore]
public void Div(int x, int y, int expected)
{
int r = arith.div(x, y);
Assert.AreEqual(r, expected);
}
}
}
4th Step: Lastly, test the service. The code we added will utilize three different values to calculate the output and provide relevant test results.
As you can see, all three values passed using the test methods passed the testing case.
Scenario #2: Skipping Tests
In case you want to skip some methods from the test. To do so, MSTest offers the functionality of the [Ignore] attribute.
To understand its functionality, replace the code used in the previous test case with the following one, including the [Ignore] attribute.
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitTestDemo.Services;
namespace TestDemo
{
[TestClass]
public class ArithDynamicDataTest
{
ArithService arith = new ArithService();
[DataTestMethod]
[DynamicData(nameof(AddData), DynamicDataSourceType.Method)]
public void Add(int x, int y, int expected)
{
int r = arith.add(x, y);
Assert.AreEqual(r, expected);
}
[DataTestMethod]
[DynamicData(nameof(SubData), DynamicDataSourceType.Method)]
public void Sub(int x, int y, int expected)
{
int r = arith.sub(x, y);
Assert.AreEqual(r, expected);
}
[DataTestMethod]
[DynamicData(nameof(MulData), DynamicDataSourceType.Method)]
public void Mul(int x, int y, int expected)
{
int r = arith.mul(x, y);
Assert.AreEqual(r, expected);
}
[DataTestMethod]
[DynamicData(nameof(DivData), DynamicDataSourceType.Method)]
public void Div(int x, int y, int expected)
{
int r = arith.div(x, y);
Assert.AreEqual(r, expected);
}
private static IEnumerable<object[]> AddData()
{
return new[]
{
new object[] { 1, 2, 3 },
new object[] { 2, 2, 4 },
new object[] { -1, 4, 3 }
};
}
private static IEnumerable<object[]> SubData()
{
return new[]
{
new object[] { 1, 2, -1 },
new object[] { 2, 2, 0 },
new object[] { 3, 2, 1 }
};
}
private static IEnumerable<object[]> MulData()
{
return new[]
{
new object[] { 9, 3, 27 },
new object[] { 3, 3, 9 },
new object[] { -3, -3, 9 }
};
}
private static IEnumerable<object[]> DivData()
{
return new[]
{
new object[] { 9, 3, 3 },
new object[] { 3, 3, 1 },
new object[] { 8, 2, 4 }
};
}
}
}
Now, run the tests, and you’ll see that two tests are skipped, where we utilized the ignore attribute. The rest of the tests are passed.
Scenario #3: Dynamic Data Testing
To write a test case for passing the dynamic data, the [DynamicData] attribute will be used with MSTest. It will help you pass the data from external sources to the defined methods to validate their work.
Step 1: Generate a new test file with the following code.
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitTestDemo.Services;
namespace TestDemo
{
[TestClass]
public class ArithDynamicDataTest
{
ArithService arith = new ArithService();
[DataTestMethod]
[DynamicData(nameof(AddData), DynamicDataSourceType.Method)]
public void Add(int x, int y, int expected)
{
int r = arith.add(x, y);
Assert.AreEqual(r, expected);
}
[DataTestMethod]
[DynamicData(nameof(SubData), DynamicDataSourceType.Method)]
public void Sub(int x, int y, int expected)
{
int r = arith.sub(x, y);
Assert.AreEqual(r, expected);
}
[DataTestMethod]
[DynamicData(nameof(MulData), DynamicDataSourceType.Method)]
public void Mul(int x, int y, int expected)
{
int r = arith.mul(x, y);
Assert.AreEqual(r, expected);
}
[DataTestMethod]
[DynamicData(nameof(DivData), DynamicDataSourceType.Method)]
public void Div(int x, int y, int expected)
{
int r = arith.div(x, y);
Assert.AreEqual(r, expected);
}
private static IEnumerable<object[]> AddData()
{
return new[]
{
new object[] { 1, 2, 3 },
new object[] { 2, 2, 4 },
new object[] { -1, 4, 3 }
};
}
private static IEnumerable<object[]> SubData()
{
return new[]
{
new object[] { 1, 2, -1 },
new object[] { 2, 2, 0 },
new object[] { 3, 2, 1 }
};
}
private static IEnumerable<object[]> MulData()
{
return new[]
{
new object[] { 9, 3, 27 },
new object[] { 3, 3, 9 },
new object[] { -3, -3, 9 }
};
}
private static IEnumerable<object[]> DivData()
{
return new[]
{
new object[] { 9, 3, 3 },
new object[] { 3, 3, 1 },
new object[] { 8, 2, 4 }
};
}
}
}
Step 2: Run the tests to view the output.
As you can see in the output, the dynamic data has passed all the 16 tests. We haven’t used the [Ignore] attribute this time, so no test is skipped.
Similarly, you can write your own test cases using MSTest to ensure that your application works efficiently and provides the correct output.
Wrapping Up
.NET Core unit testing is streamlined with the help of available frameworks, such as MSTest, NUnit, and xUnit. You can utilize any of them to write test cases and verify the application functionality. But, according to professionals, MSTest is considered a reliable choice. By following the above process, MSTest can be utilized seamlessly to conduct parametrized, skip case, and dynamic input testing cases.
Additionally, you can create your own .NET Core unit tests and ensure that your software works completely fine.
Expert in Software & Web App Engineering
Parag Mehta, the CEO and Founder of Positiwise Software Pvt Ltd has extensive knowledge of the development niche. He is implementing custom strategies to craft highly-appealing and robust applications for its clients and supporting employees to grow and ace the tasks. He is a consistent learner and always provides the best-in-quality solutions, accelerating productivity.