NUnit

unit testing.NETC#testing frameworkopen sourceassertionstest-driven development

NUnit

Overview

NUnit is an open-source unit testing framework for all .NET languages. As of 2024, the latest version is NUnit 4.3.2, supporting a wide range of environments from .NET Framework to .NET Core. It provides comprehensive testing solutions for all testing scenarios, from Test-Driven Development (TDD) to full-fledged system and integration testing.

Details

Key Features

Modern Framework Design

  • NUnit 4 leverages the latest .NET features and C# language constructs
  • Minimum supported targets are .NET Framework 4.6.2 and .NET 6.0
  • Cross-platform support (Windows, macOS, Linux)

Rich Assertion Capabilities

  • Fluent assertion model for readable test descriptions
  • Assert.That() method for high readability
  • Constraint-based assertions (Is., Does., Has., Contains.)

Flexible Test Attributes

  • Comprehensive attribute set including [TestFixture], [Test], [TestCase]
  • Powerful support for parameterized tests
  • Flexible fixture management with [SetUp], [TearDown]

Performance Improvements

  • Dramatically improved test performance for large codebases
  • Fixed long-standing performance issue with CollectionAssert.AreEquivalent
  • Optimized deep comparison of collections to be closer to O(n)

Pros and Cons

Pros

  1. Mature Ecosystem: Stability and reliability established through years of development
  2. Rich Documentation: Comprehensive official documentation and community support
  3. IDE Integration: Excellent integration with Visual Studio, Rider, and VS Code
  4. Flexibility: Supports everything from simple unit tests to complex integration tests
  5. Wide Support: Compatible with all .NET languages and framework versions

Cons

  1. Learning Curve: Rich feature set may feel complex for beginners
  2. Setup Complexity: Initial configuration can be cumbersome for large projects
  3. Performance: Execution time can be a concern for very large test suites
  4. Migration Cost: Migrating from NUnit 3 to NUnit 4 requires careful preparation

Reference Links

Code Examples

Basic Test Structure

using NUnit.Framework;

[TestFixture]
public class CalculatorTests
{
    private Calculator _calculator;

    [SetUp]
    public void Setup()
    {
        _calculator = new Calculator();
    }

    [Test]
    public void Add_TwoPositiveNumbers_ReturnsCorrectSum()
    {
        // Arrange
        int a = 5;
        int b = 3;
        int expected = 8;

        // Act
        int result = _calculator.Add(a, b);

        // Assert
        Assert.That(result, Is.EqualTo(expected));
    }
}

Parameterized Tests

[TestFixture]
public class MathOperationsTests
{
    [TestCase(2, 3, 5)]
    [TestCase(10, 15, 25)]
    [TestCase(-1, 1, 0)]
    [TestCase(0, 0, 0)]
    public void Add_VariousInputs_ReturnsExpectedResult(int a, int b, int expected)
    {
        var calculator = new Calculator();
        int result = calculator.Add(a, b);
        Assert.That(result, Is.EqualTo(expected));
    }

    [TestCase(10, 2, ExpectedResult = 5)]
    [TestCase(15, 3, ExpectedResult = 5)]
    [TestCase(100, 10, ExpectedResult = 10)]
    public int Divide_ValidInputs_ReturnsQuotient(int dividend, int divisor)
    {
        var calculator = new Calculator();
        return calculator.Divide(dividend, divisor);
    }
}

Advanced Assertion Usage

[TestFixture]
public class AdvancedAssertionTests
{
    [Test]
    public void StringAssertions_Examples()
    {
        string actual = "Hello, World!";
        
        Assert.That(actual, Does.StartWith("Hello"));
        Assert.That(actual, Does.EndWith("World!"));
        Assert.That(actual, Does.Contain("World"));
        Assert.That(actual, Is.Not.Null.And.Not.Empty);
    }

    [Test]
    public void CollectionAssertions_Examples()
    {
        var numbers = new List<int> { 1, 2, 3, 4, 5 };
        
        Assert.That(numbers, Has.Count.EqualTo(5));
        Assert.That(numbers, Contains.Item(3));
        Assert.That(numbers, Is.All.GreaterThan(0));
        Assert.That(numbers, Is.Ordered.Ascending);
    }

    [Test]
    public void ExceptionAssertions_Examples()
    {
        var calculator = new Calculator();
        
        Assert.Throws<DivideByZeroException>(() => calculator.Divide(10, 0));
        
        var ex = Assert.Throws<ArgumentException>(() => 
            calculator.CalculateSquareRoot(-1));
        Assert.That(ex.Message, Does.Contain("negative"));
    }
}

Fixture Lifecycle Management

[TestFixture]
public class DatabaseTests
{
    private DatabaseConnection _connection;
    private TestDataBuilder _dataBuilder;

    [OneTimeSetUp]
    public void OneTimeSetup()
    {
        // Executed once for the entire test fixture
        _connection = new DatabaseConnection("test_connection_string");
        _connection.Connect();
    }

    [SetUp]
    public void Setup()
    {
        // Executed before each test method
        _dataBuilder = new TestDataBuilder(_connection);
        _dataBuilder.CreateTestData();
    }

    [Test]
    public void GetUser_ValidId_ReturnsUser()
    {
        // Arrange
        int userId = _dataBuilder.CreateTestUser("John", "[email protected]");
        var userService = new UserService(_connection);

        // Act
        var user = userService.GetUser(userId);

        // Assert
        Assert.That(user, Is.Not.Null);
        Assert.That(user.Name, Is.EqualTo("John"));
        Assert.That(user.Email, Is.EqualTo("[email protected]"));
    }

    [TearDown]
    public void TearDown()
    {
        // Executed after each test method
        _dataBuilder.CleanupTestData();
    }

    [OneTimeTearDown]
    public void OneTimeTearDown()
    {
        // Executed once for the entire test fixture
        _connection.Disconnect();
    }
}

Asynchronous Testing

[TestFixture]
public class AsyncTests
{
    [Test]
    public async Task GetDataAsync_ValidRequest_ReturnsData()
    {
        // Arrange
        var service = new DataService();
        var request = new DataRequest { Id = 123 };

        // Act
        var result = await service.GetDataAsync(request);

        // Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result.Id, Is.EqualTo(123));
    }

    [Test]
    public void AsyncOperation_Timeout_ThrowsException()
    {
        var service = new SlowService();
        
        Assert.ThrowsAsync<TimeoutException>(async () => 
            await service.SlowOperationAsync(TimeSpan.FromMilliseconds(100)));
    }
}

Test Categorization with Categories and Tags

[TestFixture]
[Category("Integration")]
public class IntegrationTests
{
    [Test]
    [Category("Database")]
    [Category("Slow")]
    public void DatabaseIntegration_Test()
    {
        // Database integration test
    }

    [Test]
    [Category("API")]
    public void ApiIntegration_Test()
    {
        // API integration test
    }
}

[TestFixture]
[Category("Unit")]
public class UnitTests
{
    [Test]
    [Category("Fast")]
    public void Unit_Test()
    {
        // Fast unit test
    }
}