Previous Swagger-OpenAPI-Documentation REST vs gRPC vs Message Brokers Next

πŸ§ͺ Unit Testing and Integration Testing in ASP.NET Core

API Testing in .NET Core

In .NET Core, testing APIs involves two main approaches: unit testing and integration testing. A common practice is to have more unit tests, a moderate number of integration tests, and very few end-to-end tests β€” known as the testing pyramid.

Unit Testing

Unit testing focuses on testing individual components of your application in isolation from infrastructure and dependencies.

  • Goal: Verify that a single method or unit of work performs the expected business logic.
  • Isolation: Dependencies like databases or HTTP context are mocked.
  • Tools: xUnit, NUnit, MSTest, Moq, NSubstitute etc...

    🧰 Tools Commonly Used

    • xUnit: Popular testing framework
    • Moq: For mocking dependencies
    • TestServer: For in-memory integration testing
    • FluentAssertions: For expressive assertions
  • Pattern: Arrange - Act - Assert (AAA).

Example: Unit Testing a Controller with Moq

// Service Interface
public interface IProductService
{
    Task<Product?> GetProductByIdAsync(int id);
}

// Controller
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;
    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> Get(int id)
    {
        var product = await _productService.GetProductByIdAsync(id);
        if (product == null)
            return NotFound();
        return product;
    }
}

βœ… Unit Testing Example

// ProductService.cs
public class ProductService
{
    public decimal CalculateDiscount(decimal price) => price * 0.9m;
}

// ProductServiceTests.cs
public class ProductServiceTests
{
    [Fact]
    public void CalculateDiscount_ReturnsCorrectValue()
    {
        var service = new ProductService();
        var result = service.CalculateDiscount(100);
        Assert.Equal(90, result);
    }
}

Unit Test another Example

// Unit Test
public class ProductsControllerTests
{
    [Fact]
    public async Task Get_ProductExists_ReturnsProduct()
    {
        var mockService = new Mock<IProductService>();
        mockService.Setup(s => s.GetProductByIdAsync(1))
                   .ReturnsAsync(new Product { Id = 1, Name = "Test Product" });
        var controller = new ProductsController(mockService.Object);

        var result = await controller.Get(1);

        var okResult = Assert.IsType<ActionResult<Product>>(result);
        var product = Assert.IsType<Product>(okResult.Value);
        Assert.Equal(1, product.Id);
    }
}

Integration Testing

Integration testing validates that multiple components work together within the full application pipeline.

  • Goal: Ensure the full API pipeline works correctly.
  • Dependencies: Real or in-memory implementations (like InMemoryDb).
  • Tools: xUnit, NUnit, WebApplicationFactory.

βœ… Integration Testing Example

// ProductsController.cs
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    public IActionResult Get() => Ok(new[] { "Laptop", "Phone" });
}

// ProductsIntegrationTests.cs
public class ProductsIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public ProductsIntegrationTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task Get_ReturnsProductList()
    {
        var response = await _client.GetAsync("/api/products");
        response.EnsureSuccessStatusCode();

        var content = await response.Content.ReadAsStringAsync();
        Assert.Contains("Laptop", content);
    }
}

Another Example: Integration Testing with WebApplicationFactory

// Integration Test
public class ProductsApiIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

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

    [Fact]
    public async Task Get_ReturnsSuccessStatusCodeAndCorrectContentType()
    {
        var client = _factory.CreateClient();
        var response = await client.GetAsync("/api/products");
        response.EnsureSuccessStatusCode();
        Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
    }
}

πŸ” What’s the Difference?

  • Unit Testing: Tests individual components (e.g., controllers, services) in isolation using mocks or stubs.
  • Integration Testing: Tests the full stack (controllers, middleware, database, etc.) to verify end-to-end behavior.

Key Differences Summary

Aspect Unit Testing Integration Testing
Scope Tests a single component in isolation Tests multiple components working together
Dependencies Mocks and stubs Real or in-memory services
Speed Very fast Slower due to setup
Confidence Logic-level confidence System-level confidence
Tools xUnit, NUnit, Moq WebApplicationFactory, xUnit

βœ… When to Use Each

  • Unit Tests: For fast, isolated logic validation (services, helpers, controllers)
  • Integration Tests: For verifying real-world behavior (routing, middleware, DB access)

🚫 When Not to Use

  • Unit Tests: Avoid testing external systems (e.g., databases, APIs)
  • Integration Tests: Avoid over-testing trivial logic or duplicating unit tests

🌟 Advantages

  • Improves code reliability and maintainability
  • Facilitates refactoring with confidence
  • Helps catch bugs early in development

⚠️ Disadvantages

  • Requires setup and maintenance
  • Integration tests can be slower and more brittle
  • Over-testing can lead to false confidence or wasted effort

🧯 Precautions

  • Mock dependencies in unit tests to isolate logic
  • Use test databases or in-memory stores for integration tests
  • Run tests in CI/CD pipelines to catch regressions

🧠 Best Practices

  • Follow AAA pattern: Arrange, Act, Assert
  • Use meaningful test names and organize by feature
  • Keep tests fast and deterministic
  • Use coverage tools to identify gaps
Back to Index
Previous Swagger-OpenAPI-Documentation REST vs gRPC vs Message Brokers Next
*