Testing ASP.NET Core Applications

Effective testing is crucial for building robust and reliable ASP.NET Core applications. This section covers various strategies and tools for testing your web applications, including unit tests, integration tests, and end-to-end tests.

Unit Testing

Unit tests focus on testing individual components or methods in isolation. In ASP.NET Core, you'll often unit test your services, controllers (without their dependencies), and other business logic classes.

Common Unit Testing Frameworks

Example: Testing a Simple Service

Consider a service responsible for calculating discounts.


public class DiscountService
{
    public decimal CalculateDiscount(decimal price, int quantity)
    {
        if (price <= 0 || quantity <= 0)
        {
            throw new ArgumentException("Price and quantity must be positive.");
        }
        if (quantity > 10)
        {
            return price * 0.10m; // 10% discount for more than 10 items
        }
        return 0;
    }
}
            

A unit test for this service using xUnit:


using Xunit;
using MyWebApp.Services; // Assuming DiscountService is in this namespace

public class DiscountServiceTests
{
    [Fact]
    public void CalculateDiscount_HighQuantity_ReturnsTenPercent()
    {
        // Arrange
        var service = new DiscountService();
        decimal price = 100.00m;
        int quantity = 15;
        decimal expectedDiscount = 10.00m;

        // Act
        decimal actualDiscount = service.CalculateDiscount(price, quantity);

        // Assert
        Assert.Equal(expectedDiscount, actualDiscount);
    }

    [Fact]
    public void CalculateDiscount_LowQuantity_ReturnsZero()
    {
        // Arrange
        var service = new DiscountService();
        decimal price = 50.00m;
        int quantity = 5;
        decimal expectedDiscount = 0;

        // Act
        decimal actualDiscount = service.CalculateDiscount(price, quantity);

        // Assert
        Assert.Equal(expectedDiscount, actualDiscount);
    }

    [Fact]
    public void CalculateDiscount_InvalidInput_ThrowsArgumentException()
    {
        // Arrange
        var service = new DiscountService();
        decimal price = -10.00m;
        int quantity = 5;

        // Act & Assert
        Assert.Throws(() => service.CalculateDiscount(price, quantity));
    }
}
            

Integration Testing

Integration tests verify the interaction between different components of your application, such as controllers interacting with services or the database. ASP.NET Core provides a rich set of tools for integration testing your web applications.

WebApplicationFactory

The Microsoft.AspNetCore.Mvc.Testing package provides the WebApplicationFactory<TStartup> class, which is instrumental in creating a TestServer instance for your application. This allows you to send HTTP requests to your application without needing to host it on a real server.


using Microsoft.AspNetCore.Mvc.Testing;
using System.Net.Http;
using Xunit;

public class IntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public IntegrationTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData("/Index")]
    [InlineData("/Privacy")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/html; charset=utf-8",
            response.Content.Headers.ContentType?.ToString());
    }
}
            
Note: For integration tests, you often need to configure your application to use an in-memory database or a dedicated test database to avoid affecting your production data.

Mocking

Mocking is essential for isolating the component under test, especially in unit and integration testing. It allows you to replace real dependencies with mock objects that simulate their behavior.

Popular Mocking Libraries

Example: Mocking a Repository


// Using Moq
using Moq;
using MyWebApp.Data; // Assuming IProductRepository is defined here
using MyWebApp.Models;
using System.Collections.Generic;

public class ProductServiceTestsWithMock
{
    [Fact]
    public void GetProducts_ReturnsAllProducts()
    {
        // Arrange
        var mockRepository = new Mock<IProductRepository>();
        var products = new List<Product>
        {
            new Product { Id = 1, Name = "Laptop" },
            new Product { Id = 2, Name = "Mouse" }
        };
        mockRepository.Setup(repo => repo.GetAll()).Returns(products);

        var productService = new ProductService(mockRepository.Object); // ProductService depends on IProductRepository

        // Act
        var result = productService.GetAllProducts();

        // Assert
        Assert.Equal(2, result.Count);
        Assert.Equal("Laptop", result[0].Name);
        mockRepository.Verify(repo => repo.GetAll(), Times.Once); // Ensure the repository method was called
    }
}
            

End-to-End (E2E) Testing

E2E tests simulate real user scenarios by interacting with the application through its user interface. Tools like Selenium or Playwright are commonly used for this purpose.

While E2E tests are valuable, they are typically slower and more brittle than unit or integration tests. They are best used for critical user flows.

Testing Strategies

By adopting a comprehensive testing strategy, you can significantly improve the quality, maintainability, and stability of your ASP.NET Core applications.

For more detailed information, please refer to the official ASP.NET Core Testing Documentation.