Skip to main content

Overview

The Intent.AspNetCore.IntegrationTesting module generates complete integration test infrastructure for testing your ASP.NET Core Web API endpoints end-to-end. It creates test fixtures, handles authentication, manages test databases, and provides helpers for API testing.
Module ID: Intent.AspNetCore.IntegrationTesting
Version: 2.0.0+
Dependencies: Intent.AspNetCore, Intent.EntityFrameworkCore

What Gets Generated

This module generates the following components:

Test Fixtures

WebApplicationFactory-based fixtures with test database configuration

HTTP Client Helpers

Authenticated HTTP clients and request builders

Database Management

Test database creation, seeding, and cleanup

Authentication Mocks

Mock authentication for testing secured endpoints

Installation

Intent.AspNetCore.IntegrationTesting
This module works with:
  • Intent.AspNetCore
  • Intent.EntityFrameworkCore
  • Intent.Application.MediatR

Generated Test Infrastructure

Web Application Factory

The module generates a custom WebApplicationFactory that configures a test environment:
IntegrationTestWebApplicationFactory.cs
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

public class IntegrationTestWebApplicationFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            // Remove the app's DbContext registration
            var descriptor = services.SingleOrDefault(
                d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>));
            if (descriptor != null)
            {
                services.Remove(descriptor);
            }

            // Add DbContext using in-memory database for testing
            services.AddDbContext<ApplicationDbContext>(options =>
            {
                options.UseInMemoryDatabase("IntegrationTestDb");
            });

            // Build service provider
            var sp = services.BuildServiceProvider();

            // Create database and seed test data
            using var scope = sp.CreateScope();
            var scopedServices = scope.ServiceProvider;
            var db = scopedServices.GetRequiredService<ApplicationDbContext>();
            
            db.Database.EnsureCreated();
            SeedTestData(db);
        });
    }

    private void SeedTestData(ApplicationDbContext context)
    {
        // Add test data here
    }
}

Base Test Class

BaseIntegrationTest.cs
using System.Net.Http;
using Xunit;

public class BaseIntegrationTest : IClassFixture<IntegrationTestWebApplicationFactory>
{
    protected readonly HttpClient Client;
    protected readonly IntegrationTestWebApplicationFactory Factory;

    public BaseIntegrationTest(IntegrationTestWebApplicationFactory factory)
    {
        Factory = factory;
        Client = factory.CreateClient();
    }

    protected async Task<HttpResponseMessage> GetAsync(string url)
    {
        return await Client.GetAsync(url);
    }

    protected async Task<HttpResponseMessage> PostAsync<T>(string url, T content)
    {
        return await Client.PostAsJsonAsync(url, content);
    }

    protected async Task<HttpResponseMessage> PutAsync<T>(string url, T content)
    {
        return await Client.PutAsJsonAsync(url, content);
    }

    protected async Task<HttpResponseMessage> DeleteAsync(string url)
    {
        return await Client.DeleteAsync(url);
    }
}

Writing Integration Tests

Testing GET Endpoints

ProductsControllerTests.cs
using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Xunit;

public class ProductsControllerTests : BaseIntegrationTest
{
    public ProductsControllerTests(IntegrationTestWebApplicationFactory factory) 
        : base(factory) { }

    [Fact]
    public async Task GetProducts_ReturnsSuccessStatusCode()
    {
        // Act
        var response = await GetAsync("/api/products");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
    }

    [Fact]
    public async Task GetProduct_WithValidId_ReturnsProduct()
    {
        // Arrange
        var productId = Guid.NewGuid();
        
        // Act
        var response = await GetAsync($"/api/products/{productId}");
        var product = await response.Content.ReadFromJsonAsync<ProductDto>();

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        product.Should().NotBeNull();
        product.Id.Should().Be(productId);
    }
}

Testing POST Endpoints

[Fact]
public async Task CreateProduct_WithValidData_ReturnsCreated()
{
    // Arrange
    var command = new CreateProductCommand
    {
        Name = "Test Product",
        Price = 99.99m,
        Description = "Test description"
    };

    // Act
    var response = await PostAsync("/api/products", command);
    var productId = await response.Content.ReadFromJsonAsync<Guid>();

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.Created);
    productId.Should().NotBeEmpty();
}

[Fact]
public async Task CreateProduct_WithInvalidData_ReturnsBadRequest()
{
    // Arrange
    var command = new CreateProductCommand
    {
        Name = "", // Invalid: empty name
        Price = -10 // Invalid: negative price
    };

    // Act
    var response = await PostAsync("/api/products", command);

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}

Testing with Authentication

For endpoints that require authentication, the module provides authentication helpers:
AuthenticatedIntegrationTest.cs
using System.Net.Http.Headers;

public class AuthenticatedIntegrationTest : BaseIntegrationTest
{
    public AuthenticatedIntegrationTest(IntegrationTestWebApplicationFactory factory) 
        : base(factory)
    {
        // Configure authenticated client
        var token = GetTestAuthToken();
        Client.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Bearer", token);
    }

    private string GetTestAuthToken()
    {
        // Generate test JWT token or use mock authentication
        return "test-token";
    }
}
[Fact]
public async Task GetSecuredResource_WithAuthentication_ReturnsSuccess()
{
    // Act
    var response = await GetAsync("/api/secured-resource");

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.OK);
}

Database Testing Strategies

Fast tests with EF Core in-memory provider (default):
services.AddDbContext<ApplicationDbContext>(options =>
{
    options.UseInMemoryDatabase("IntegrationTestDb");
});
Pros: Fast, no setup required
Cons: Doesn’t test real database features (constraints, triggers)

Best Practices

Ensure tests don’t affect each other:
  • Use unique test data for each test
  • Clean up after tests or use transactions
  • Generate unique GUIDs for test entities
  • Consider using IAsyncLifetime for setup/teardown
Manage test data effectively:
  • Use builders or factories for test data
  • Seed minimal data needed for tests
  • Consider using Bogus for generating realistic data
  • Keep test data close to the test
Keep integration tests fast:
  • Use in-memory database for most tests
  • Only use real database when testing specific features
  • Run tests in parallel where possible
  • Cache WebApplicationFactory instance
Focus integration tests on:
  • Complete request/response cycles
  • Authentication and authorization
  • Database interactions and transactions
  • Validation at the API boundary
  • Error handling and status codes

Advanced Scenarios

Testing File Uploads

[Fact]
public async Task UploadFile_WithValidFile_ReturnsSuccess()
{
    // Arrange
    var content = new MultipartFormDataContent();
    var fileContent = new ByteArrayContent(new byte[] { 1, 2, 3 });
    fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/pdf");
    content.Add(fileContent, "file", "test.pdf");

    // Act
    var response = await Client.PostAsync("/api/files/upload", content);

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.OK);
}

Testing Background Jobs

[Fact]
public async Task ProcessOrder_TriggersBackgroundJob()
{
    // Arrange
    var command = new ProcessOrderCommand { OrderId = Guid.NewGuid() };

    // Act
    await PostAsync("/api/orders/process", command);
    await Task.Delay(1000); // Wait for background job

    // Assert
    var order = await GetOrderFromDatabase(command.OrderId);
    order.Status.Should().Be(OrderStatus.Processed);
}

Troubleshooting

Issue: Tests fail due to data from previous testsSolution:
public class BaseIntegrationTest : IAsyncLifetime
{
    public async Task InitializeAsync()
    {
        // Setup
    }

    public async Task DisposeAsync()
    {
        // Clean up database
        using var scope = Factory.Services.CreateScope();
        var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
        await context.Database.EnsureDeletedAsync();
    }
}
Issue: Authenticated endpoints return 401Solution: Configure test authentication scheme:
builder.ConfigureServices(services =>
{
    services.AddAuthentication("Test")
        .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", options => {});
});

Next Steps

CRUD Integration Tests

Auto-generate integration tests for CRUD operations

Unit Testing

Generate unit tests for application logic

MediatR Module

Learn about the CQRS pattern with MediatR

EF Core Module

Configure Entity Framework Core persistence

Build docs developers (and LLMs) love