Previous EF-core-Migrations Thundering-Herd-Problem Next

Unit Testing in .NET Core

Unit Testing in .NET

Definition: Unit testing is the practice of testing individual units or components of an application in isolation. In .NET, popular frameworks include xUnit and NUnit. xUnit and NUnit are two popular frameworks in the .NET ecosystem for writing and running unit tests.

xUnit

  • Modern, extensible, and designed for .NET Core.
  • Supports parallel test execution by default.
  • Uses constructor injection for test setup.
  • Does not use attributes like [SetUp] or [TearDown]; instead, relies on constructors and IDisposable.

NUnit

  • Older and more mature framework with broad community support.
  • Supports parameterized tests and rich assertions.
  • Uses attributes like [Test], [SetUp], [TearDown], [TestCase].
  • More flexible for legacy projects and integration with older tools.

Unit testing example in .NET Core

Framework: xUnit is the default testing framework in Visual Studio for .NET Core, while NUnit is a widely used alternative.

1. The code to be tested

Example Class: A simple Calculator class.

namespace MyMathLibrary;

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public int Subtract(int a, int b)
    {
        return a - b;
    }
}
  

2. Create the test project

Command Line: dotnet new xunit -o MyMathLibrary.Tests

Visual Studio: Add a new project and choose "xUnit Test Project".

3. Add a project reference

Command Line: dotnet add MyMathLibrary.Tests/MyMathLibrary.Tests.csproj reference MyMathLibrary/MyMathLibrary.csproj

Visual Studio: Right-click Dependencies > Add Project Reference.

4. Write the tests (xUnit example)

Test Class: Create CalculatorTests.cs.

using Xunit;
using MyMathLibrary;

namespace MyMathLibrary.Tests;

public class CalculatorTests
{
    [Fact]
    public void Add_GivenTwoNumbers_ReturnsCorrectSum()
    {
        var calculator = new Calculator();
        int a = 5;
        int b = 3;
        int expectedResult = 8;
        int actualResult = calculator.Add(a, b);
        Assert.Equal(expectedResult, actualResult);
    }

    [Theory]
    [InlineData(10, 5, 5)]
    [InlineData(0, 0, 0)]
    [InlineData(-5, -2, -3)]
    public void Subtract_GivenTwoNumbers_ReturnsCorrectDifference(int a, int b, int expectedResult)
    {
        var calculator = new Calculator();
        int actualResult = calculator.Subtract(a, b);
        Assert.Equal(expectedResult, actualResult);
    }
}
  

4. NUnit comparison

using NUnit.Framework;
using MyMathLibrary;

namespace MyMathLibrary.Tests;

[TestFixture]
public class CalculatorTests
{
    private Calculator _calculator;

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

    [Test]
    public void Add_GivenTwoNumbers_ReturnsCorrectSum()
    {
        int actualResult = _calculator.Add(5, 3);
        Assert.AreEqual(8, actualResult);
    }

    [TestCase(10, 5, 5)]
    [TestCase(0, 0, 0)]
    [TestCase(-5, -2, -3)]
    public void Subtract_GivenTwoNumbers_ReturnsCorrectDifference(int a, int b, int expectedResult)
    {
        int actualResult = _calculator.Subtract(a, b);
        Assert.AreEqual(expectedResult, actualResult);
    }
}
  

5. Run the tests

Command Line: dotnet test

Visual Studio: Use Test Explorer to run tests.

Advantages of unit testing

  • Early Bug Detection: Finds issues early in development.
  • Improved Code Quality: Encourages clean and modular code.
  • Faster Debugging: Points directly to the failing unit.
  • Safe Refactoring: Provides confidence when modifying code.
  • Living Documentation: Tests describe expected behavior.

Disadvantages of unit testing

  • Initial Effort: Requires setup and time to write tests.
  • False Security: High coverage does not guarantee correctness.
  • Integration Gaps: Does not test interaction between components.
  • Maintenance Overhead: Tests must be updated with code changes.

Best practices

  • Follow AAA: Arrange, Act, Assert format for all tests.
  • Mock Dependencies: Use mocking tools to isolate units.
  • Write Focused Tests: Test one behavior per method.
  • Use Descriptive Names: Clearly state the test purpose.
  • Cover Edge Cases: Include nulls, invalid inputs, and exceptions.
  • Write small, focused tests for each unit.
  • Use meaningful test names that describe behavior.
  • Keep tests independent and isolated.
  • Use mocks/stubs for external dependencies.
  • Run tests frequently during development.
  • Use assertions wisely to validate expected outcomes.
  • Organize tests in a consistent structure.
  • Use parameterized tests to cover multiple scenarios.

Precautions

  • Avoid Testing Frameworks: Do not test .NET or library functionality.
  • Keep Tests Simple: Avoid complex logic in test code.
  • Ensure Independence: Tests should not depend on execution order.
  • Avoid Over-Specifying: Do not tightly couple tests to implementation.
  • Separate Code: Keep test code in a separate project.
  • Don’t test implementation details—focus on behavior.
  • Avoid writing tests that depend on external systems (e.g., databases, APIs).
  • Don’t ignore failing tests—fix or remove them.
  • Ensure test coverage but don’t obsess over 100% coverage.
  • Don’t use unit tests for performance or UI testing.
  • Be cautious with shared state between tests.
Back to Index
Previous EF-core-Migrations Thundering-Herd-Problem Next
*