Testing in C# (.NET): Overview of Frameworks and Tools
High-quality testing is the foundation of reliable software on the .NET platform. The C# ecosystem has several mature frameworks for unit, integration, and acceptance testing. In this article, we will review the main tools, their features, and provide practical code examples.
Main Frameworks for Unit Testing
In .NET Core and .NET 5+, built-in testing support is implemented through three main frameworks:
- MSTest — the classic framework from Microsoft, simple for beginners.
- NUnit — a popular open-source framework with a rich set of attributes.
- xUnit.net — a modern, lightweight, and flexible tool created by the author of NUnit.
All three are supported in Visual Studio, Rider, and via the dotnet CLI. The choice depends on team preferences, but xUnit and NUnit are considered more flexible for complex scenarios.
Example Test with xUnit
Let's look at testing a simple calculator using xUnit:
using Xunit;
public class Calculator{ public int Add(int a, int b) => a + b; public int Divide(int a, int b) => b == 0 ? throw new DivideByZeroException() : a / b;}
public class CalculatorTests{ [Fact] public void Add_ShouldReturnSum_WhenTwoNumbers() { // Arrange var calc = new Calculator();
// Act var result = calc.Add(3, 5);
// Assert Assert.Equal(8, result); }
[Theory] [InlineData(10, 2, 5)] [InlineData(9, 3, 3)] [InlineData(7, 1, 7)] public void Divide_ShouldReturnQuotient(int a, int b, int expected) { var calc = new Calculator(); var result = calc.Divide(a, b); Assert.Equal(expected, result); }
[Fact] public void Divide_ShouldThrow_WhenDivideByZero() { var calc = new Calculator(); Assert.Throws<DivideByZeroException>(() => calc.Divide(5, 0)); }}The [Fact] attribute denotes a regular test, while [Theory] with [InlineData] allows running one test with different input data.
Tools for Mock Objects
In unit testing, it is often necessary to isolate the class under test from external dependencies (databases, APIs, file system). Mock frameworks are used for this purpose:
- Moq — the most popular, simple syntax, good documentation.
- NSubstitute — more concise, with a fluent interface.
- FakeItEasy — a friendly API for quickly creating stubs.
Example Using Moq
Suppose we have a service that sends emails. We want to test the logic without actual sending:
using Moq;using Xunit;
public interface IEmailSender{ void Send(string to, string subject, string body);}
public class NotificationService{ private readonly IEmailSender _sender; public NotificationService(IEmailSender sender) => _sender = sender;
public void NotifyUser(string email, string message) { if (string.IsNullOrWhiteSpace(email)) throw new ArgumentException("Email is required"); _sender.Send(email, "Notification", message); }}
public class NotificationServiceTests{ [Fact] public void NotifyUser_ShouldCallSend_WhenValidEmail() { // Arrange var mockSender = new Mock<IEmailSender>(); var service = new NotificationService(mockSender.Object);
// Act service.NotifyUser("test@example.com", "Hello!");
// Assert mockSender.Verify(s => s.Send("test@example.com", "Notification", "Hello!"), Times.Once); } [ Also in library
Testing in Kotlin: Frameworks and Tools for Quality Code
Rust WebSocket Libraries: Tokio-tungstenite and async-tungstenite for Real-Time
Working with Excel in JavaScript (Node.js): Libraries and Code Examples
Twilio for JavaScript (Node.js): Complete Guide with Examples