Learn .NET C# Testing: Unit & Integration Tests

Testing is no longer optional in modern development, especially when you’re working in .NET C#. By following this tutorial, you can lay a strong foundation in writing both unit and integration tests using tools like xUnit, Moq, and Entity Framework Core.

Let’s dive in and turn you into a confident test-driven developer, step by step.

Why Testing is Essential for .NET Developers

Catching Bugs Early

Bugs in production can ruin user experience and increase maintenance costs. Tests act as a safety net, finding problems before they reach your users.

Cleaner, More Reliable Code

Tests enforce better design. When you’re forced to write testable code, you naturally keep things modular, separated, and well-defined.

Understanding Unit vs Integration Tests

What is a Unit Test?

A unit test verifies a small piece of code, like a single method. It’s isolated—free from any external dependencies.

Example: Testing a method that adds two numbers.

What is an Integration Test?

Integration tests verify how multiple parts of your system work together, like saving a user to a database or calling an API.

Example: Testing a service that interacts with EF Core.

AAA Pattern: Arrange, Act, Assert

All good tests follow the AAA pattern:

  • Arrange: Set up data and dependencies.
  • Act: Invoke the method.
  • Assert: Check the expected outcome.

Choosing the Right Tools for Testing in .NET

Test Frameworks: xUnit, MSTest, NUnit

All three are excellent. For modern .NET, xUnit is a favorite due to its extensibility and community support.

Mocking with Moq

Mocks help you simulate complex dependencies like services or repositories, without actually calling them.

Test Runners

Use the dotnet test command or Visual Studio Test Explorer to run your tests with ease.

Setting Up Your .NET Testing Environment (10 Min)

Install Prerequisites

  • .NET SDK (6.0+ recommended)
  • Visual Studio or VS Code

Create Projects

dotnet new console -n MyApp
dotnet new xunit -n MyApp.Tests

Link and Add References

dotnet sln add MyApp/MyApp.csproj
dotnet sln add MyApp.Tests/MyApp.Tests.csproj
cd MyApp.Tests
dotnet add reference ../MyApp/MyApp.csproj
dotnet add package Moq

Run tests:

dotnet test

Your First Unit Test with xUnit (15 Min)

Step 1: Create Calculator

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

Step 2: Add Unit Test

public class CalculatorTests
{
[Fact]
public void Add_TwoNumbers_ReturnsSum()
{
var calculator = new Calculator();
var result = calculator.Add(2, 3);
Assert.Equal(5, result);
}
}

Run it. Boom! Your first test.

Mocking Dependencies with Moq (15 Min)

Step 1: Create Interface and Service

public interface IUserRepository
{
string GetUserName(int userId);
}

public class UserService
{
private readonly IUserRepository _repo;
public UserService(IUserRepository repo) => _repo = repo;

public string GreetUser(int id) => $"Hello, {_repo.GetUserName(id)}!";
}

Step 2: Test with Moq

var mockRepo = new Mock<IUserRepository>();
mockRepo.Setup(r => r.GetUserName(1)).Returns("Alice");

var service = new UserService(mockRepo.Object);
var result = service.GreetUser(1);

Assert.Equal("Hello, Alice!", result);

Nice! You’ve tested a service without needing a real database.

Writing a Simple Integration Test (10 Min)

Set Up EF Core

dotnet add package Microsoft.EntityFrameworkCore.InMemory

Create AppDbContext

public class AppDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}

public class User { public int Id; public string Name; }

Write Integration Test

var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("TestDb").Options;

using var context = new AppDbContext(options);
context.Users.Add(new User { Id = 1, Name = "Bob" });
context.SaveChanges();

var saved = context.Users.Find(1);
Assert.Equal("Bob", saved.Name);

Real DB simulation, zero config.

Best Practices to Write Effective Tests

  • Keep tests short and focused.
  • Use clear naming: Method_Scenario_ExpectedOutcome.
  • Prefer mocks over real external systems.
  • Don’t chase 100% coverage, test what matters.

Advanced Tips and Tools

  • FluentAssertions makes your asserts look better.
  • coverlet.msbuild measures code coverage:
dotnet add package coverlet.msbuild
dotnet test --collect:"XPlat Code Coverage"

Practical Exercise: Divide Method Testing

Add Method

public int Divide(int a, int b)
{
if (b == 0) throw new DivideByZeroException();
return a / b;
}

Test It

[Fact]
public void Divide_ByZero_Throws()
{
var calc = new Calculator();
Assert.Throws<DivideByZeroException>(() => calc.Divide(10, 0));
}

Exception tested.

FAQs: Unit & Integration Testing in .NET C#

1. What’s the difference between unit and integration tests?

Unit tests check isolated components. Integration tests check how components work together, including DBs or APIs.

2. Is xUnit better than NUnit or MSTest?

All are solid. xUnit is modern and widely used, especially with .NET Core.

3. Can I mock async methods with Moq?

Absolutely. Use .ReturnsAsync() in your Setup().

4. Should I test private methods?

Only indirectly. If private logic is crucial, refactor it into its own testable class.

5. How much test coverage is enough?

80% is a solid benchmark. But focus on critical logic, not everything.

6. How do I run tests automatically in CI/CD?

Use dotnet test in your pipeline (e.g., GitHub Actions, Azure DevOps).

Keep building, keep testing, and your code will thank you.

Recommended Resources to Dive Deeper

Leave a Comment

Your email address will not be published. Required fields are marked *