Unit Testing in ASP.NET Core

• By OmerZ Solutions

Modern software applications require reliability, maintainability, and continuous improvement. As ASP.NET Core applications grow in complexity, manually verifying every feature becomes difficult and time-consuming.

Unit testing helps developers verify individual parts of an application by testing small pieces of code independently. It improves software quality, reduces unexpected failures, and makes future changes safer.

Unit Testing in ASP.NET Core
Unit Testing is a software testing approach where individual components such as methods, services, and business logic are tested separately to ensure they work correctly.

What is Unit Testing?

Unit testing is the process of testing the smallest testable parts of an application, usually individual methods or classes, without depending on external systems.

A unit test focuses on verifying one specific behavior. It should execute quickly, provide clear results, and help developers identify problems early during development.

Example of a Simple Unit

Consider a calculator service:



public class CalculatorService
{
    public int Add(int firstNumber, int secondNumber)
    {
        return firstNumber + secondNumber;
    }
}

The Add method performs one specific operation, making it an ideal candidate for unit testing.

Why Unit Testing Matters in ASP.NET Core

ASP.NET Core applications commonly contain controllers, services, repositories, database operations, and external integrations. Without automated tests, changes can introduce unexpected problems.

Benefits of Unit Testing

  • Improves application reliability
  • Detects bugs early in development
  • Makes refactoring safer
  • Improves code quality
  • Supports continuous integration and deployment
  • Provides documentation through executable examples

Unit Testing vs Integration Testing

Although both testing approaches are important, they serve different purposes.

Unit Testing Integration Testing
Tests individual components independently Tests multiple components working together
Uses mocked dependencies Uses real dependencies such as databases or APIs
Runs very quickly Usually takes more time
Focuses on code logic Focuses on system behavior

Popular Unit Testing Frameworks for ASP.NET Core

.NET developers have several testing frameworks available. The most commonly used frameworks are:

  • xUnit
  • NUnit
  • MSTest

xUnit Testing Framework

xUnit is one of the most popular testing frameworks for modern ASP.NET Core applications.

It provides simple syntax, parallel test execution, and excellent integration with .NET development tools.

Installing xUnit



dotnet add package xunit
dotnet add package xunit.runner.visualstudio

Creating a Unit Test Project

A separate test project is usually created to keep production code and testing code organized.



dotnet new xunit -n Application.Tests

Writing Your First Unit Test

A typical unit test follows three important steps:

  • Arrange
  • Act
  • Assert

Arrange Act Assert Pattern

The AAA pattern provides a clear structure for writing readable tests.

  • Arrange: Prepare required objects and test data.
  • Act: Execute the method being tested.
  • Assert: Verify the expected result.

Example Unit Test



public class CalculatorTests
{
    [Fact]
    public void Add_ShouldReturnCorrectValue()
    {

        // Arrange
        var calculator = new CalculatorService();

        // Act
        var result = calculator.Add(10,20);

        // Assert
        Assert.Equal(30,result);
    }

}

Testing Services in ASP.NET Core

In a well-designed ASP.NET Core application, business logic is usually placed inside service classes instead of controllers. Unit testing services allows developers to verify application behavior without depending on databases, APIs, or other external resources.

Example Service Class



public class OrderService
{
    public decimal CalculateDiscount(decimal amount, decimal discountPercentage)
    {
        return amount - (amount * discountPercentage / 100);
    }
}

Unit Test for Service



public class OrderServiceTests
{

    [Fact]
    public void CalculateDiscount_ShouldReturnDiscountedAmount()
    {

        // Arrange
        var service = new OrderService();

        // Act
        var result =  service.CalculateDiscount(1000,10);

        // Assert
        Assert.Equal(900,result);
    }

}

Testing Dependency Injection in ASP.NET Core

ASP.NET Core applications heavily depend on dependency injection. Services, repositories, logging, and configuration objects are usually injected through constructors.

Unit tests should verify the behavior of classes without creating real dependencies.

Example Interface



public interface IEmailService
{
    bool SendEmail(string email);
}

Service Using Dependency Injection



public class UserRegistrationService
{

private readonly IEmailService _emailService;


public UserRegistrationService(IEmailService emailService)
{
    _emailService = emailService;
}

public bool RegisterUser(string email)
{
    return _emailService.SendEmail(email);
}

}

Mocking Dependencies with Moq

Mocking allows developers to create fake versions of dependencies during unit testing. This keeps tests isolated and independent from external systems.

Moq is one of the most commonly used mocking libraries in .NET applications.

Installing Moq



dotnet add package Moq

Mocking Email Service Example



public class UserRegistrationTests
{

[Fact]
public void RegisterUser_ShouldSendEmail()
{

var emailMock = new Mock<IEmailService>();
emailMock.Setup(x => x.SendEmail("test@test.com")).Returns(true);
var service = new UserRegistrationService(emailMock.Object);
var result = service.RegisterUser("test@test.com");
Assert.True(result);
emailMock.Verify(x => x.SendEmail("test@test.com"),Times.Once);

}

}

Testing Tip: Mock external dependencies such as email services, payment gateways, third-party APIs, and file systems to keep unit tests fast and reliable.

Testing Repository Layer

Repositories are responsible for database communication. In unit testing, repositories are commonly tested using mocked database contexts or in-memory databases.

Repository Interface



public interface IProductRepository
{

Product GetProduct(int id);

}

Repository Implementation



public class ProductRepository : IProductRepository
{

private readonly ApplicationDbContext _context;


public ProductRepository(ApplicationDbContext context)
{
    _context = context;
}

public Product GetProduct(int id)
{

return _context.Products.FirstOrDefault(x => x.Id == id);

}

}

Testing Entity Framework Core with In-Memory Database

EF Core provides an in-memory database provider that allows developers to test database-related functionality without using a real database server.

Install Package



dotnet add package Microsoft.EntityFrameworkCore.InMemory

Creating Test Database Context



private ApplicationDbContext CreateContext()
{

var options = new DbContextOptionsBuilder<ApplicationDbContext>().UseInMemoryDatabase("TestDatabase").Options;
return new ApplicationDbContext(options);

}

Testing Repository Data



[Fact]

public void GetProduct_ShouldReturnProduct()
{

var context = CreateContext();
context.Products.Add(new Product{Id = 1,Name = "Laptop"});
context.SaveChanges();

var repository = new ProductRepository(context);
var result = repository.GetProduct(1);
Assert.NotNull(result);

}

Testing ASP.NET Core Web API Controllers

Controllers should contain minimal logic, but they still need testing to ensure correct HTTP responses and application behavior.

Example API Controller



[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{

private readonly IProductService _service;

public ProductsController(IProductService service)
{
    _service = service;
}

[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{

var product = _service.GetProduct(id);

if(product == null)
{
    return NotFound();
}

return Ok(product);

}

}

Controller Unit Test



public class ProductsControllerTests
{

[Fact]
public void GetProduct_WhenProductExists_ReturnsOk()
{

var serviceMock = new Mock<IProductService>();
serviceMock.Setup(x => x.GetProduct(1)).Returns(new Product{Id = 1,Name = "Laptop"});

var controller = new ProductsController(serviceMock.Object);

var result = controller.GetProduct(1);

Assert.IsType<OkObjectResult>(result);

}

}

Testing Validation Logic

Validation rules should also be tested to ensure invalid data is rejected before processing.

Example Validation Service



public class AccountValidator
{

public bool IsValidEmail(string email)
{

return email.Contains("@");

}

}

Validation Test



[Fact]
public void InvalidEmail_ShouldReturnFalse()
{

var validator = new AccountValidator();

var result = validator.IsValidEmail("invalid-email");
Assert.False(result);

}

Test Driven Development (TDD) in ASP.NET Core

Test Driven Development is a software development approach where developers write tests before implementing the actual functionality.

TDD follows a simple cycle:

  • Red: Write a failing test.
  • Green: Write the minimum code required to pass the test.
  • Refactor: Improve code quality while keeping tests successful.

TDD Example

Suppose we need a service that calculates employee bonuses. The first step is creating a test.



[Fact]
public void CalculateBonus_ShouldReturnCorrectAmount()
{

var service = new EmployeeService();
var result = service.CalculateBonus(50000);
Assert.Equal(5000,result);

}

After creating the test, the required implementation is added and improved through continuous refactoring.

Testing Asynchronous Methods in ASP.NET Core

Modern ASP.NET Core applications use asynchronous programming extensively. Unit tests should support async methods using async and await keywords.

Async Service Example



public async Task<Product> GetProductAsync(int id)
{
	return await _repository.GetByIdAsync(id);
}

Async Unit Test Example



[Fact]
public async Task GetProductAsync_ShouldReturnProduct()
{

var result = await service.GetProductAsync(1);
Assert.NotNull(result);

}

Testing Authentication and Authorization

ASP.NET Core applications often contain protected resources that require authentication and authorization testing.

Controller tests should verify that unauthorized users cannot access protected operations.

Authorization Example



[Authorize]
[HttpGet]
public IActionResult GetProfile()
{
	return Ok();
}

Testing Claims and User Identity



var claims = new List<Claim> {new Claim(ClaimTypes.Name,"Admin")};

var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);

controller.ControllerContext = new ControllerContext
{
	HttpContext = new DefaultHttpContext{User = principal}
};

Testing Application Configuration

Configuration values such as connection settings, API keys, and feature flags should also be tested when they affect application behavior.

Using Configuration Mock



var configuration = new ConfigurationBuilder().AddInMemoryCollection(
new Dictionary<string,string> {{"ApplicationName","MyApp"}})
.Build();

Testing Logging in ASP.NET Core

Logging is important for monitoring applications. While logs are usually not the main focus of unit tests, developers can verify important logging behavior.

Mock Logger Example



var loggerMock = new Mock<ILogger<OrderService>>();
var service = new OrderService(loggerMock.Object);

Code Coverage in ASP.NET Core

Code coverage measures how much application code is executed during automated tests.

Higher coverage does not always mean better software quality, but it helps identify areas without sufficient testing.

Installing Coverage Tool



dotnet add package coverlet.collector

Running Tests with Coverage



dotnet test --collect:"XPlat Code Coverage"

Coverage reports can be generated and integrated into CI/CD pipelines.

Unit Testing with Continuous Integration

Modern development teams run automated tests whenever code changes are pushed to source control.

Unit tests can be integrated with:

  • GitHub Actions
  • Azure DevOps Pipelines
  • Jenkins
  • GitLab CI/CD

Example CI Test Command



dotnet restore

dotnet build

dotnet test

Unit Testing Best Practices

  • Write small and focused tests.
  • Test one behavior per test method.
  • Use meaningful test names.
  • Keep tests independent from each other.
  • Avoid testing framework functionality.
  • Mock external dependencies.
  • Prefer readable test code over complex logic.
  • Maintain tests when application requirements change.

Recommended Unit Test Naming Convention

A good test name explains the expected behavior.



MethodName_StateUnderTest_ExpectedResult

Examples



Login_InvalidPassword_ReturnsFalse

CreateOrder_ValidCustomer_ReturnsSuccess

GetUser_UserExists_ReturnsUser

Common Unit Testing Mistakes

  • Writing tests only after bugs appear.
  • Creating tests that depend on databases or external APIs.
  • Testing private methods directly.
  • Writing large and complicated test methods.
  • Ignoring failed tests.
  • Creating unrealistic test scenarios.

Unit Testing Strategy for Enterprise ASP.NET Core Applications

Enterprise applications usually contain multiple layers, and each layer requires a different testing approach.

Application Layer Recommended Testing
Business Services Unit Tests with Mock Dependencies
Repositories Integration Tests or In-Memory Database Tests
Controllers Unit Tests for HTTP Behavior
Database Integration Testing

Benefits of Automated Testing in .NET Projects

  • Faster development cycles
  • Reduced production defects
  • Safer application updates
  • Better team collaboration
  • Improved software confidence

Frequently Asked Questions About Unit Testing in ASP.NET Core

What is the purpose of unit testing in ASP.NET Core?

The purpose of unit testing is to verify that individual components such as services, methods, and business logic work correctly without depending on external systems.

Which unit testing framework is best for ASP.NET Core?

xUnit is one of the most widely used testing frameworks for ASP.NET Core applications because of its simplicity, modern design, and excellent .NET integration. NUnit and MSTest are also popular choices.

Should controllers be unit tested in ASP.NET Core?

Yes. Controllers should be tested to verify HTTP responses, validation behavior, authorization rules, and interaction with application services.

Should I test private methods?

Generally, private methods should not be tested directly. Instead, test the public methods that use those private implementations. This keeps tests focused on application behavior.

What is mocking in unit testing?

Mocking creates fake implementations of dependencies so that a class can be tested independently. For example, a payment service can be mocked instead of calling a real payment provider during testing.

Can Entity Framework Core be unit tested?

Yes. EF Core code can be tested using approaches such as the in-memory database provider, SQLite test databases, or integration testing with a real database.

How many unit tests should an ASP.NET Core application have?

There is no fixed number of tests required. The goal is to provide meaningful coverage for important business rules, critical workflows, and areas where changes may introduce risks.

Unit Testing Checklist for ASP.NET Core Projects

  • Create a separate test project for application tests.
  • Use a consistent testing framework across the team.
  • Follow Arrange-Act-Assert structure.
  • Keep tests independent and repeatable.
  • Mock external dependencies.
  • Test business logic thoroughly.
  • Include tests in CI/CD pipelines.
  • Review and maintain tests regularly.

Conclusion

Unit testing is an essential practice for building reliable and maintainable ASP.NET Core applications. It allows developers to validate application logic, detect problems early, and confidently introduce new features.

By combining testing frameworks such as xUnit, mocking libraries such as Moq, dependency injection, and automated CI/CD execution, development teams can create high-quality software with fewer production issues.

A strong unit testing strategy does not only improve code quality; it also helps organizations deliver software faster while maintaining stability and long-term maintainability.

Need Help Building Reliable .NET Applications?

OmerZ Solutions helps businesses develop scalable, secure, and maintainable ASP.NET Core applications using modern software engineering practices.

Contact Us