Skip to content

Integration Testing - Audit Trail Platform (ATP)

Real dependencies, real confidence — ATP's integration testing strategy validates service interactions with actual databases, message buses, caches, and storage using WebApplicationFactory, TestServer, Docker containers (Testcontainers), and Azure Pipelines service containers to ensure production-like behavior and catch integration issues before deployment.


📋 Documentation Generation Plan

This document will be generated in 18 cycles. Current progress:

Cycle Topics Estimated Lines Status
Cycle 1 Integration Testing Fundamentals (1-2) ~3,500 ⏳ Not Started
Cycle 2 WebApplicationFactory & TestServer (3-4) ~4,500 ⏳ Not Started
Cycle 3 Test Startup & Configuration (5-6) ~4,000 ⏳ Not Started
Cycle 4 Database Integration Testing (7-8) ~5,000 ⏳ Not Started
Cycle 5 Messaging Integration Testing (9-10) ~5,000 ⏳ Not Started
Cycle 6 Cache Integration Testing (11-12) ~3,500 ⏳ Not Started
Cycle 7 Storage Integration Testing (13-14) ~4,000 ⏳ Not Started
Cycle 8 API Endpoint Testing (15-16) ~4,500 ⏳ Not Started
Cycle 9 Cross-Service Integration (17-18) ~4,500 ⏳ Not Started
Cycle 10 Docker Containers & Testcontainers (19-20) ~5,000 ⏳ Not Started
Cycle 11 Test Data Management (21-22) ~4,000 ⏳ Not Started
Cycle 12 Test Isolation & Cleanup (23-24) ~3,500 ⏳ Not Started
Cycle 13 Authentication & Authorization Testing (25-26) ~3,500 ⏳ Not Started
Cycle 14 Multi-Tenant Testing (27-28) ~4,000 ⏳ Not Started
Cycle 15 Performance & Load Testing (29-30) ~4,000 ⏳ Not Started
Cycle 16 CI/CD Integration (31-32) ~4,000 ⏳ Not Started
Cycle 17 Troubleshooting & Debugging (33-34) ~3,500 ⏳ Not Started
Cycle 18 Best Practices & Anti-Patterns (35-36) ~3,500 ⏳ Not Started

Total Estimated Lines: ~76,000


Purpose & Scope

This document provides the complete integration testing implementation guide for ATP, covering WebApplicationFactory, TestServer, Docker containers (Testcontainers, Azure Pipelines service containers), real dependency testing (databases, messaging, caches, storage), API endpoint testing, cross-service integration, test data management, isolation strategies, and CI/CD integration for comprehensive, production-like integration validation.

Why Integration Testing for ATP?

  1. Real Dependencies: Test against actual databases, message buses, caches (not mocks)
  2. Catch Integration Issues: Serialization, connection pooling, timeouts, retries
  3. Validate Configurations: Connection strings, retry policies, circuit breakers
  4. ORM/Driver Validation: Ensure NHibernate mappings, MongoDB drivers work correctly
  5. Transaction Behavior: Real transaction boundaries, rollback, isolation levels
  6. Message Bus Integration: End-to-end message publish/consume with real Service Bus
  7. Production Parity: Use same container images as production
  8. Regression Prevention: Catch breaking changes in dependencies
  9. Confidence: High confidence before deployment
  10. Compliance: Validate audit trail integrity with real storage

Integration Testing vs. Unit Testing

Unit Testing:
- Fast (<1 second per test)
- Isolated (mocks/stubs)
- Focus: Single component logic
- Example: Repository.Save() with mocked database

Integration Testing:
- Slower (1-30 seconds per test)
- Real dependencies (database, message bus, cache)
- Focus: Component interactions
- Example: API endpoint → repository → real database → verify data persisted

ATP Integration Testing Stack

Testing Framework:
- MSTest (or xUnit)
- WebApplicationFactory (ASP.NET Core)
- TestServer (in-process HTTP server)

Real Dependencies (Containers):
- SQL Server (Testcontainers / Azure Pipelines)
- MongoDB (Testcontainers / Azure Pipelines)
- Redis (Testcontainers / Azure Pipelines)
- Azure Service Bus (local emulator or test namespace)
- Azure Blob Storage (Azurite emulator)
- Seq (structured log viewing)

Test Tools:
- Testcontainers.NET (programmatic containers)
- WireMock.NET (HTTP API mocking)
- MassTransit Test Harness (message bus testing)
- FluentAssertions (fluent assertions)
- Moq (for partial mocking when needed)

Detailed Cycle Plan

CYCLE 1: Integration Testing Fundamentals (~3,500 lines)

Topic 1: Integration Testing Philosophy

What will be covered: - What is Integration Testing?

Definition:
Integration testing validates that multiple components
work together correctly when integrated with real dependencies.

Scope:
- Component A + Component B interaction
- Service + Database interaction
- Service + Message Bus interaction
- Service + External API interaction
- Full HTTP request → response flow

ATP Examples:
- Ingestion API → NHibernate → SQL Server
- Projection Service → MassTransit → Service Bus → Consumer
- Query API → Repository → Database → Verify data returned
- Export Service → Blob Storage → Verify file created
- Gateway → Authentication → Ingestion Service

  • Integration Testing Pyramid for ATP

                      /\
                     /  \  E2E Tests (Reqnroll)
                    /____\
                   /      \  Cross-Service Integration
                  /________\
                 /          \  Service Integration
                /____________\
               /              \  Component Integration
              /________________\
    
    Component Integration (70%):
    - Single service + real dependencies
    - Example: Ingestion API + SQL Server + Redis
    - Fast (5-10 seconds per test)
    
    Service Integration (20%):
    - Multiple services together
    - Example: Gateway → Ingestion → Query
    - Moderate (10-30 seconds per test)
    
    Cross-Service Integration (8%):
    - Full system integration
    - Example: End-to-end audit trail flow
    - Slower (30-60 seconds per test)
    
    E2E Tests (2%):
    - User journeys (Reqnroll/BDD)
    - Slowest (60+ seconds per test)
    

  • When to Use Integration Tests vs. Unit Tests

    Use Integration Tests When:
    ✅ Testing ORM/driver behavior (NHibernate, MongoDB)
    ✅ Testing message bus serialization/deserialization
    ✅ Testing database constraints and transactions
    ✅ Testing connection pooling and retries
    ✅ Testing real HTTP request/response flow
    ✅ Testing authentication/authorization flows
    ✅ Testing configuration behavior (connection strings)
    ✅ Testing multi-tenant isolation (RLS, partitioning)
    
    Use Unit Tests When:
    ✅ Testing business logic (domain models)
    ✅ Testing algorithms and calculations
    ✅ Testing validation rules
    ✅ Testing transformations and mappings
    ✅ Fast feedback (TDD cycle)
    
    Example: Audit Record Validation
    
    Unit Test:
    - Mock repository
    - Test validation logic: "Title cannot be empty"
    - Fast, isolated
    
    Integration Test:
    - Real repository + real database
    - Test: "INSERT fails when Title is empty"
    - Validates database constraint enforcement
    

  • Integration Testing Principles

    1. Real Dependencies
       - Use real databases, message buses, caches
       - No mocks for external dependencies
       - Use Testcontainers or service containers
    
    2. Isolation
       - Each test runs in isolation
       - Clean up test data (database cleanup, queue purging)
       - No test dependencies (test A doesn't rely on test B)
    
    3. Deterministic
       - Same input → same output
       - No randomness (use fixed test data)
       - Control time (use TimeProvider for timestamps)
    
    4. Fast Enough
       - Integration tests are slower than unit tests
       - Target: <30 seconds per test
       - Parallelize when possible (isolated databases)
    
    5. Production Parity
       - Use same container images as production
       - Same database versions
       - Same configuration patterns
    
    6. Comprehensive Coverage
       - Happy path
       - Error paths (timeouts, failures)
       - Edge cases (nulls, empty collections, large payloads)
    

Code Examples: - Integration testing concepts - Pyramid examples - Test type decision matrix

Diagrams: - Integration testing pyramid - Test scope comparison - Dependency interaction

Deliverables: - Integration testing fundamentals guide - Decision matrix (unit vs. integration) - Philosophy document


Topic 2: Integration Testing Strategy Overview

What will be covered: - ATP Integration Testing Strategy

Layers of Integration Testing:

1. Component Integration
   - Single service + real dependencies
   - Example: IngestionService + SQL Server
   - Test project: ConnectSoft.Audit.Ingestion.IntegrationTests

2. Service Integration
   - HTTP endpoints + database + cache
   - Example: POST /api/v1/ingest → SQL → Redis
   - Test project: ConnectSoft.Audit.Ingestion.IntegrationTests

3. Cross-Service Integration
   - Multiple services together
   - Example: Gateway → Ingestion → Query
   - Test project: ConnectSoft.Audit.CrossService.IntegrationTests

4. End-to-End Integration
   - Full system (BDD with Reqnroll)
   - Test project: ConnectSoft.Audit.AcceptanceTests

  • Test Project Structure

    tests/
    ├── ConnectSoft.Audit.Ingestion.IntegrationTests/
    │   ├── Api/
    │   │   ├── IngestionEndpointTests.cs
    │   │   └── HealthCheckTests.cs
    │   ├── Persistence/
    │   │   ├── RepositoryTests.cs
    │   │   └── TransactionTests.cs
    │   ├── Messaging/
    │   │   ├── OutboxTests.cs
    │   │   └── EventPublisherTests.cs
    │   └── Infrastructure/
    │       ├── DatabaseFixture.cs
    │       └── TestContainerSetup.cs
    ├── ConnectSoft.Audit.Query.IntegrationTests/
    │   └── ...
    ├── ConnectSoft.Audit.CrossService.IntegrationTests/
    │   ├── GatewayToIngestionTests.cs
    │   └── IngestionToQueryTests.cs
    └── ConnectSoft.Audit.AcceptanceTests/ (BDD)
        └── ...
    

  • Dependency Management

  • Testcontainers for local development
  • Azure Pipelines service containers for CI/CD
  • Docker Compose for manual testing

Code Examples: - Project structure examples - Test organization patterns

Diagrams: - Test project structure - Integration layers

Deliverables: - Strategy document - Project structure guide


CYCLE 2: WebApplicationFactory & TestServer (~4,500 lines)

Topic 3: WebApplicationFactory Fundamentals

What will be covered: - What is WebApplicationFactory?

// WebApplicationFactory creates an in-process test host
// Runs your ASP.NET Core application without launching a separate process

public class IngestionWebApplicationFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        // Override configuration for testing
        builder.ConfigureAppConfiguration((context, config) =>
        {
            config.AddInMemoryCollection(new Dictionary<string, string>
            {
                ["ConnectionStrings:AuditDb"] = _testDbConnectionString,
                ["ConnectionStrings:Redis"] = _testRedisConnectionString,
                ["Environment"] = "Testing"
            });
        });

        // Replace services with test doubles if needed
        builder.ConfigureServices(services =>
        {
            // Remove production services
            var descriptor = services.SingleOrDefault(
                d => d.ServiceType == typeof(IHttpClientFactory));

            if (descriptor != null)
            {
                services.Remove(descriptor);
            }

            // Add test services
            services.AddSingleton<IHttpClientFactory, TestHttpClientFactory>();
        });
    }
}

// Usage in tests
[TestClass]
public class IngestionEndpointTests
{
    private readonly IngestionWebApplicationFactory _factory;
    private HttpClient _client;

    public IngestionEndpointTests()
    {
        _factory = new IngestionWebApplicationFactory();
    }

    [TestInitialize]
    public void Setup()
    {
        _client = _factory.CreateClient();
    }

    [TestMethod]
    public async Task IngestEvent_Should_ReturnSuccess()
    {
        // Arrange
        var auditEvent = new AuditEvent
        {
            TenantId = "test-tenant",
            EventType = "user.login",
            Payload = JsonSerializer.Serialize(new { userId = "user-123" })
        };

        // Act
        var response = await _client.PostAsJsonAsync("/api/v1/ingest", auditEvent);

        // Assert
        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);

        var result = await response.Content.ReadFromJsonAsync<IngestResult>();
        Assert.IsNotNull(result.AuditRecordId);
    }

    [TestCleanup]
    public void Cleanup()
    {
        _client?.Dispose();
        _factory?.Dispose();
    }
}

  • Benefits of WebApplicationFactory

    ✅ In-Process Execution
       - No separate process (faster startup)
       - Direct DI container access
       - Easier debugging (breakpoints work)
    
    ✅ Full Application Stack
       - All middleware (authentication, authorization, routing)
       - All filters (validation, exception handling)
       - All controllers and endpoints
    
    ✅ Configuration Override
       - Override appsettings for testing
       - Use test database connection strings
       - Enable test-specific features
    
    ✅ Service Replacement
       - Replace external services with test doubles
       - Use mock HTTP clients for external APIs
       - Replace expensive services with in-memory versions
    
    ✅ Test Isolation
       - Each test gets fresh application instance
       - Can configure per-test or per-class
    
    ✅ Fast Execution
       - No network overhead (in-process)
       - No port binding conflicts
       - Parallel execution possible
    

  • WebApplicationFactory vs. TestServer

    WebApplicationFactory (Recommended):
    - Higher-level abstraction
    - Uses TestServer internally
    - Easier to configure
    - Better for integration tests
    - Example: Full HTTP request/response testing
    
    TestServer (Lower-level):
    - More control over server configuration
    - Direct access to server features
    - Better for custom scenarios
    - Example: WebSocket testing, custom middleware
    

Code Examples: - Complete WebApplicationFactory implementation - Configuration override patterns - Service replacement examples - Test base classes

Diagrams: - WebApplicationFactory architecture - Test execution flow

Deliverables: - WebApplicationFactory guide - Setup patterns - Configuration examples


Topic 4: TestServer Configuration

What will be covered: - TestServer Setup

public class IngestionTestServer
{
    private TestServer _server;
    private HttpClient _client;

    public async Task InitializeAsync()
    {
        var hostBuilder = Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<TestStartup>();
                webBuilder.UseTestServer();  // ← Enables TestServer
            })
            .ConfigureAppConfiguration((context, config) =>
            {
                config.AddJsonFile("appsettings.Testing.json");
            });

        var host = await hostBuilder.StartAsync();
        _server = host.GetTestServer();
        _client = _server.CreateClient();
    }

    public HttpClient Client => _client;

    public async Task DisposeAsync()
    {
        _client?.Dispose();
        _server?.Dispose();
    }
}

  • TestStartup Pattern

    // Custom startup for testing
    public class TestStartup : Startup
    {
        public TestStartup(IConfiguration configuration, IWebHostEnvironment environment)
            : base(configuration, environment)
        {
        }
    
        public override void ConfigureServices(IServiceCollection services)
        {
            // Drop and recreate test database
            DropAndCreateTestDatabase();
    
            // Call base configuration
            base.ConfigureServices(services);
    
            // Override services for testing
            services.AddSingleton<IExternalApiClient, MockExternalApiClient>();
        }
    
        public override void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            // Call base configuration (middleware, routing)
            base.Configure(app, env);
    
            // Additional test-specific middleware
            app.UseMiddleware<TestAuthenticationMiddleware>();
        }
    }
    

  • Authentication in Tests

    // Test authentication middleware (bypass real auth)
    public class TestAuthenticationMiddleware
    {
        private readonly RequestDelegate _next;
    
        public async Task InvokeAsync(HttpContext context)
        {
            // Extract test tenant/user from headers
            var testTenantId = context.Request.Headers["X-Test-Tenant-Id"].FirstOrDefault();
            var testUserId = context.Request.Headers["X-Test-User-Id"].FirstOrDefault();
    
            if (testTenantId != null)
            {
                var claims = new List<Claim>
                {
                    new Claim("tenant_id", testTenantId),
                    new Claim("user_id", testUserId ?? "test-user"),
                    new Claim(ClaimTypes.Role, "audit:write")
                };
    
                var identity = new ClaimsIdentity(claims, "Test");
                context.User = new ClaimsPrincipal(identity);
            }
    
            await _next(context);
        }
    }
    
    // Usage in tests
    [TestMethod]
    public async Task IngestEvent_WithTenantContext_ShouldSucceed()
    {
        _client.DefaultRequestHeaders.Add("X-Test-Tenant-Id", "acme-corp");
        _client.DefaultRequestHeaders.Add("X-Test-User-Id", "user-123");
    
        var response = await _client.PostAsJsonAsync("/api/v1/ingest", auditEvent);
        // ...
    }
    

Code Examples: - TestServer setup - TestStartup implementations - Authentication patterns - Middleware customization

Diagrams: - TestServer architecture - Startup flow

Deliverables: - TestServer guide - TestStartup patterns - Authentication guide


CYCLE 3: Test Startup & Configuration (~4,000 lines)

Topic 5: Test Configuration Management

What will be covered: - appsettings.Testing.json

{
  "ConnectionStrings": {
    "AuditDb": "Server=localhost,1433;Database=ATP_Test;User Id=sa;Password=TestPassword123!;TrustServerCertificate=true",
    "Redis": "localhost:6379",
    "ServiceBus": "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=..."
  },
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}"
        }
      }
    ]
  },
  "Azure": {
    "BlobStorage": {
      "ConnectionString": "UseDevelopmentStorage=true"  // Azurite emulator
    }
  },
  "Testing": {
    "UseTestContainers": true,
    "SkipDatabaseCleanup": false,
    "EnableDetailedLogging": true
  }
}

  • Environment-Specific Configuration
  • Local development (Testcontainers)
  • CI/CD (Azure Pipelines service containers)
  • Docker Compose for manual testing

Code Examples: - Configuration files (all environments) - Configuration builder patterns

Deliverables: - Configuration guide


Topic 6: Test Fixtures & Base Classes

What will be covered: - Base Test Class Pattern

public abstract class IntegrationTestBase
{
    protected IngestionWebApplicationFactory Factory { get; private set; }
    protected HttpClient Client { get; private set; }
    protected IServiceProvider Services { get; private set; }
    protected string TestTenantId { get; } = "test-tenant-001";

    [TestInitialize]
    public virtual void TestInitialize()
    {
        Factory = new IngestionWebApplicationFactory();
        Client = Factory.CreateClient();
        Services = Factory.Services;

        // Set default test headers
        Client.DefaultRequestHeaders.Add("X-Test-Tenant-Id", TestTenantId);
        Client.DefaultRequestHeaders.Add("X-Test-User-Id", "test-user");

        // Seed test data
        SeedTestData();
    }

    [TestCleanup]
    public virtual void TestCleanup()
    {
        // Clean up test data
        CleanupTestData();

        Client?.Dispose();
        Factory?.Dispose();
    }

    protected abstract void SeedTestData();
    protected abstract void CleanupTestData();
}

// Usage
[TestClass]
public class IngestionEndpointTests : IntegrationTestBase
{
    protected override void SeedTestData()
    {
        // Create test tenant, policies, etc.
    }

    protected override void CleanupTestData()
    {
        // Delete test audit records
        var repository = Services.GetRequiredService<IAuditRecordRepository>();
        repository.DeleteByTenantAsync(TestTenantId);
    }

    [TestMethod]
    public async Task IngestEvent_Should_Succeed()
    {
        // Test implementation
    }
}

Code Examples: - Base test classes - Fixture patterns - Setup/teardown strategies

Deliverables: - Test fixture guide


CYCLE 4: Database Integration Testing (~5,000 lines)

Topic 7: SQL Server Integration Testing

What will be covered: - Testcontainers for SQL Server

using Testcontainers.MsSql;

[TestClass]
public class DatabaseIntegrationTests : IAsyncLifetime
{
    private MsSqlContainer _sqlContainer;
    private string _connectionString;

    public async Task InitializeAsync()
    {
        // Start SQL Server container
        _sqlContainer = new MsSqlBuilder()
            .WithImage("mcr.microsoft.com/mssql/server:2022-latest")
            .WithPassword("TestPassword123!")
            .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(1433))
            .Build();

        await _sqlContainer.StartAsync();
        _connectionString = _sqlContainer.GetConnectionString();

        // Run migrations
        await RunMigrationsAsync(_connectionString);
    }

    public async Task DisposeAsync()
    {
        await _sqlContainer.DisposeAsync();
    }

    [TestMethod]
    public async Task SaveAuditRecord_Should_PersistToDatabase()
    {
        // Arrange
        var repository = CreateRepository(_connectionString);
        var auditRecord = new AuditRecord
        {
            Id = Ulid.NewUlid(),
            TenantId = "test-tenant",
            EventType = "user.login",
            OccurredAtUtc = DateTime.UtcNow
        };

        // Act
        await repository.SaveAsync(auditRecord);

        // Assert
        var retrieved = await repository.GetByIdAsync(auditRecord.Id);
        Assert.IsNotNull(retrieved);
        Assert.AreEqual(auditRecord.EventType, retrieved.EventType);
    }

    [TestMethod]
    public async Task Transaction_Rollback_Should_NotPersist()
    {
        // Arrange
        var repository = CreateRepository(_connectionString);
        var unitOfWork = CreateUnitOfWork(_connectionString);

        // Act
        await unitOfWork.BeginTransactionAsync();

        var record1 = new AuditRecord { Id = Ulid.NewUlid(), TenantId = "test" };
        await repository.SaveAsync(record1);

        var record2 = new AuditRecord { Id = Ulid.NewUlid(), TenantId = "test" };
        await repository.SaveAsync(record2);

        await unitOfWork.RollbackAsync();

        // Assert
        var retrieved1 = await repository.GetByIdAsync(record1.Id);
        var retrieved2 = await repository.GetByIdAsync(record2.Id);

        Assert.IsNull(retrieved1);
        Assert.IsNull(retrieved2);
    }
}

  • NHibernate Integration Tests

    [TestMethod]
    public async Task NHibernateMapping_Should_CorrectlyPersistEntity()
        {
            // Arrange
            var sessionFactory = CreateNHibernateSessionFactory(_connectionString);
            using var session = sessionFactory.OpenSession();
    
            var entity = new AuditRecordEntity
            {
                ObjectId = Guid.NewGuid(),
                TenantId = "test-tenant",
                EventType = "user.login",
                OccurredAtUtc = DateTime.UtcNow
            };
    
            // Act
            await session.SaveAsync(entity);
            await session.FlushAsync();
    
            // Clear session to force database read
            session.Clear();
    
            // Assert
            var retrieved = await session.GetAsync<AuditRecordEntity>(entity.ObjectId);
            Assert.IsNotNull(retrieved);
            Assert.AreEqual(entity.TenantId, retrieved.TenantId);
        }
    

  • RLS (Row-Level Security) Testing

    [TestMethod]
    public async Task Query_WithTenantContext_Should_ReturnOnlyTenantData()
        {
            // Arrange
            var tenant1 = "tenant-1";
            var tenant2 = "tenant-2";
    
            await SeedDataAsync(tenant1, 10);
            await SeedDataAsync(tenant2, 10);
    
            // Act (with tenant1 context)
            await SetTenantContextAsync(tenant1);
            var tenant1Records = await repository.GetAllAsync();
    
            // Assert
            Assert.AreEqual(10, tenant1Records.Count);
            Assert.IsTrue(tenant1Records.All(r => r.TenantId == tenant1));
        }
    

Code Examples: - Complete database integration tests - Testcontainers setup - NHibernate integration - Transaction testing - RLS testing

Diagrams: - Database test architecture - Transaction flow

Deliverables: - Database integration guide - Testcontainers setup - RLS testing guide


Topic 8: MongoDB Integration Testing

What will be covered: - Testcontainers for MongoDB

using Testcontainers.MongoDb;

public class MongoDbIntegrationTests : IAsyncLifetime
{
    private MongoDbContainer _mongoContainer;
    private IMongoDatabase _database;

    public async Task InitializeAsync()
    {
        _mongoContainer = new MongoDbBuilder()
            .WithImage("mongo:7")
            .Build();

        await _mongoContainer.StartAsync();

        var client = new MongoClient(_mongoContainer.GetConnectionString());
        _database = client.GetDatabase("ATP_Test");

        // Run migrations
        await RunMongoMigrationsAsync(_database);
    }

    [TestMethod]
    public async Task SaveDocument_Should_PersistToMongoDb()
    {
        // Arrange
        var collection = _database.GetCollection<AuditRecordDocument>("AuditRecords");
        var document = new AuditRecordDocument
        {
            Id = ObjectId.GenerateNewId(),
            TenantId = "test-tenant",
            EventType = "user.login"
        };

        // Act
        await collection.InsertOneAsync(document);

        // Assert
        var retrieved = await collection.Find(d => d.Id == document.Id).FirstOrDefaultAsync();
        Assert.IsNotNull(retrieved);
        Assert.AreEqual(document.EventType, retrieved.EventType);
    }
}

Code Examples: - MongoDB integration tests - Migration testing

Deliverables: - MongoDB integration guide


CYCLE 5: Messaging Integration Testing (~5,000 lines)

Topic 9: MassTransit Integration Testing

What will be covered: - MassTransit Test Harness

using MassTransit.Testing;

[TestClass]
public class EventConsumerTests
{
    private InMemoryTestHarness _harness;
    private ConsumerTestHarness<AuditAcceptedEventConsumer> _consumerHarness;

    [TestInitialize]
    public async Task Setup()
    {
        _harness = new InMemoryTestHarness();
        _consumerHarness = _harness.Consumer<AuditAcceptedEventConsumer>();

        await _harness.Start();
    }

    [TestCleanup]
    public async Task Cleanup()
    {
        await _harness.Stop();
        _harness.Dispose();
    }

    [TestMethod]
    public async Task ConsumeEvent_Should_ProcessCorrectly()
    {
        // Arrange
        var event = new AuditAcceptedEvent
        {
            AuditRecordId = Ulid.NewUlid(),
            TenantId = "test-tenant"
        };

        // Act
        await _harness.Bus.Publish(event);

        // Assert
        Assert.IsTrue(await _consumerHarness.Consumed.Any<AuditAcceptedEvent>());

        var consumed = _consumerHarness.Consumed.Select<AuditAcceptedEvent>().First();
        Assert.AreEqual(event.AuditRecordId, consumed.Context.Message.AuditRecordId);
    }
}

  • Azure Service Bus Integration (Real)
    // Real Service Bus integration (test namespace)
    public class ServiceBusIntegrationTests : IAsyncLifetime
    {
        private ServiceBusClient _serviceBusClient;
        private ServiceBusSender _sender;
        private ServiceBusReceiver _receiver;
    
        public async Task InitializeAsync()
        {
            var connectionString = Configuration["ServiceBus:TestConnectionString"];
            _serviceBusClient = new ServiceBusClient(connectionString);
    
            var topicName = "audit-appended-v1-test";
            var subscriptionName = "test-subscription";
    
            _sender = _serviceBusClient.CreateSender(topicName);
            _receiver = _serviceBusClient.CreateReceiver(topicName, subscriptionName);
        }
    
        [TestMethod]
        public async Task PublishAndConsume_Should_DeliverMessage()
        {
            // Arrange
            var message = new ServiceBusMessage(JsonSerializer.Serialize(new AuditAppendedEvent
            {
                AuditRecordId = Ulid.NewUlid(),
                TenantId = "test-tenant"
            }));
    
            // Act
            await _sender.SendMessageAsync(message);
    
            // Assert
            var received = await _receiver.ReceiveMessageAsync(TimeSpan.FromSeconds(10));
            Assert.IsNotNull(received);
    
            var event = JsonSerializer.Deserialize<AuditAppendedEvent>(received.Body);
            Assert.AreEqual(message.TenantId, event.TenantId);
    
            await _receiver.CompleteMessageAsync(received);
        }
    }
    

Code Examples: - Complete messaging integration tests - MassTransit harness - Service Bus tests

Deliverables: - Messaging integration guide


Topic 10: Outbox/Inbox Integration Testing

What will be covered: - Transactional Outbox Testing

[TestMethod]
public async Task SaveRecordAndOutbox_Should_BeAtomic()
    {
        // Arrange
        var unitOfWork = CreateUnitOfWork();
        var recordRepository = CreateRepository(unitOfWork);
        var outboxRepository = CreateOutboxRepository(unitOfWork);

        var auditRecord = new AuditRecord { /* ... */ };
        var outboxEvent = new OutboxEvent { /* ... */ };

        // Act
        await unitOfWork.BeginTransactionAsync();
        await recordRepository.SaveAsync(auditRecord);
        await outboxRepository.AppendAsync(outboxEvent);
        await unitOfWork.CommitAsync();

        // Assert
        var record = await recordRepository.GetByIdAsync(auditRecord.Id);
        var outbox = await outboxRepository.GetPendingAsync();

        Assert.IsNotNull(record);
        Assert.AreEqual(1, outbox.Count);
    }

    [TestMethod]
    public async Task OutboxRelay_Should_PublishToServiceBus()
    {
        // Arrange
        await SeedOutboxEventsAsync(5);

        // Act
        await _outboxRelay.ProcessPendingAsync();

        // Assert
        Assert.IsTrue(await _harness.Published.Any<AuditAppendedEvent>());
        Assert.AreEqual(0, await _outboxRepository.GetPendingCountAsync());
    }

Code Examples: - Outbox/inbox tests - Idempotency testing

Deliverables: - Outbox testing guide


CYCLE 10: Docker Containers & Testcontainers (~5,000 lines)

Topic 19: Testcontainers.NET

What will be covered: - Testcontainers Setup

using Testcontainers.MsSql;
using Testcontainers.Redis;
using Testcontainers.MongoDb;

public class TestContainersFixture : IAsyncLifetime
{
    public MsSqlContainer SqlServer { get; private set; }
    public RedisContainer Redis { get; private set; }
    public MongoDbContainer MongoDb { get; private set; }

    public async Task InitializeAsync()
    {
        // Start all containers in parallel
        var tasks = new List<Task>
        {
            StartSqlServerAsync(),
            StartRedisAsync(),
            StartMongoDbAsync()
        };

        await Task.WhenAll(tasks);
    }

    private async Task StartSqlServerAsync()
    {
        SqlServer = new MsSqlBuilder()
            .WithImage("mcr.microsoft.com/mssql/server:2022-latest")
            .WithPassword("TestPassword123!")
            .WithWaitStrategy(Wait.ForUnixContainer()
                .UntilPortIsAvailable(1433)
                .UntilMessageIsLogged("SQL Server is now ready"))
            .Build();

        await SqlServer.StartAsync();
    }

    private async Task StartRedisAsync()
    {
        Redis = new RedisBuilder()
            .WithImage("redis:7-alpine")
            .Build();

        await Redis.StartAsync();
    }

    public async Task DisposeAsync()
    {
        await Task.WhenAll(
            SqlServer?.DisposeAsync().AsTask() ?? Task.CompletedTask,
            Redis?.DisposeAsync().AsTask() ?? Task.CompletedTask,
            MongoDb?.DisposeAsync().AsTask() ?? Task.CompletedTask);
    }
}

Code Examples: - Complete Testcontainers setup - Multi-container orchestration

Deliverables: - Testcontainers guide


Topic 20: Azure Pipelines Service Containers

What will be covered: - Service Container Configuration

# azure-pipelines.yml
resources:
  containers:
  - container: sqlserver
    image: mcr.microsoft.com/mssql/server:2022-latest
    env:
      ACCEPT_EULA: Y
      SA_PASSWORD: TestPassword123!
    ports:
    - 1433:1433

  - container: redis
    image: redis:7-alpine
    ports:
    - 6379:6379

  - container: mongodb
    image: mongo:7
    ports:
    - 27017:27017

jobs:
- job: IntegrationTests
  services:
    sqlserver: sqlserver
    redis: redis
    mongodb: mongodb
  steps:
  - task: DotNetCoreCLI@2
    inputs:
      command: test
      arguments: |
        --settings ConnectSoft.Audit.runsettings
        --environment ASPNETCORE_ENVIRONMENT=Testing

  • Connection String Configuration
    <!-- ConnectSoft.Audit.runsettings -->
    <RunSettings>
      <TestRunParameters>
        <Parameter name="ConnectionStrings__AuditDb" 
                   value="Server=localhost,1433;Database=ATP_Test;User Id=sa;Password=TestPassword123!;TrustServerCertificate=true" />
        <Parameter name="ConnectionStrings__Redis" 
                   value="localhost:6379" />
        <Parameter name="ConnectionStrings__MongoDb" 
                   value="mongodb://localhost:27017/ATP_Test" />
      </TestRunParameters>
    </RunSettings>
    

Code Examples: - Pipeline configurations - Runsettings examples

Deliverables: - CI/CD integration guide


CYCLE 11: Test Data Management (~4,000 lines)

Topic 21: Test Data Seeding

What will be covered: - Test Data Builder Pattern

public class AuditRecordBuilder
{
    private Ulid _id = Ulid.NewUlid();
    private string _tenantId = "test-tenant";
    private string _eventType = "user.login";
    private DateTime _occurredAtUtc = DateTime.UtcNow;
    private Dictionary<string, object> _metadata = new();

    public AuditRecordBuilder WithId(Ulid id)
    {
        _id = id;
        return this;
    }

    public AuditRecordBuilder WithTenantId(string tenantId)
    {
        _tenantId = tenantId;
        return this;
    }

    public AuditRecordBuilder WithEventType(string eventType)
    {
        _eventType = eventType;
        return this;
    }

    public AuditRecordBuilder WithMetadata(string key, object value)
    {
        _metadata[key] = value;
        return this;
    }

    public AuditRecord Build()
    {
        return new AuditRecord
        {
            Id = _id,
            TenantId = _tenantId,
            EventType = _eventType,
            OccurredAtUtc = _occurredAtUtc,
            Metadata = _metadata
        };
    }
}

// Usage
var record = new AuditRecordBuilder()
    .WithTenantId("acme-corp")
    .WithEventType("order.created")
    .WithMetadata("orderId", "order-123")
    .Build();

  • Test Data Factories
  • Seed Data Helpers

Code Examples: - Builder patterns - Factory patterns

Deliverables: - Test data guide


Topic 22: Test Data Cleanup

What will be covered: - Automatic Cleanup Strategies - Per-test cleanup - Per-fixture cleanup - Transaction rollback - Database truncation

Code Examples: - Cleanup patterns

Deliverables: - Cleanup guide


CYCLE 12: Test Isolation & Cleanup (~3,500 lines)

Topic 23: Test Isolation Strategies

What will be covered: - Database Isolation - Per-test database (slow but isolated) - Per-fixture database (faster, shared) - Transaction rollback (fastest) - Schema isolation

Code Examples: - Isolation patterns

Deliverables: - Isolation guide


Topic 24: Parallel Test Execution

What will be covered: - Parallel Test Strategies - Test method parallelization - Test class parallelization - Collection-based isolation

Code Examples: - Parallel execution setup

Deliverables: - Parallel testing guide


CYCLE 18: Best Practices & Anti-Patterns (~3,500 lines)

Topic 35: Integration Testing Best Practices

What will be covered: - Best Practices Checklist

✅ DO:
- Use real dependencies (databases, message buses)
- Clean up test data after each test
- Use Testcontainers for local development
- Use Azure Pipelines service containers for CI/CD
- Test both happy paths and error scenarios
- Use test data builders for readable tests
- Parallelize tests when possible (isolated databases)
- Keep tests deterministic (no randomness)
- Use meaningful test names (describe scenario)
- Test edge cases (nulls, empty collections, large payloads)

❌ DON'T:
- Don't use mocks for external dependencies
- Don't share test data between tests
- Don't rely on test execution order
- Don't use production databases
- Don't skip cleanup (database pollution)
- Don't hardcode connection strings
- Don't ignore flaky tests
- Don't test business logic in integration tests
- Don't create excessive test data (slow)
- Don't use real external APIs (use WireMock)

Deliverables: - Best practices guide - Anti-patterns catalog


Topic 36: Troubleshooting Integration Tests

What will be covered: - Common Issues - Container startup failures - Port conflicts - Test data pollution - Flaky tests - Timeout issues

Deliverables: - Troubleshooting guide


Summary of Deliverables

Complete integration testing implementation covering:

  1. Fundamentals: Philosophy, strategy, pyramid
  2. WebApplicationFactory & TestServer: In-process testing
  3. Test Configuration: Startup, fixtures, base classes
  4. Database Testing: SQL Server, MongoDB, Testcontainers
  5. Messaging Testing: MassTransit, Service Bus, Outbox/Inbox
  6. Cache Testing: Redis integration
  7. Storage Testing: Blob Storage (Azurite)
  8. API Testing: HTTP endpoints, authentication
  9. Cross-Service: Multi-service integration
  10. Docker Containers: Testcontainers, Azure Pipelines
  11. Test Data: Seeding, cleanup, builders
  12. Isolation: Strategies, parallel execution
  13. Multi-Tenant: RLS, tenant isolation testing
  14. Performance: Load testing integration
  15. CI/CD: Pipeline integration
  16. Operations: Troubleshooting, best practices


This integration testing guide provides complete implementation for ATP's integration test suite, from WebApplicationFactory setup and TestServer configuration to Docker containers (Testcontainers), real dependency testing (databases, messaging, caches), API endpoint validation, cross-service integration, test data management, isolation strategies, and CI/CD integration for comprehensive, production-like integration validation with high confidence before deployment.