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.