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?
- Real Dependencies: Test against actual databases, message buses, caches (not mocks)
- Catch Integration Issues: Serialization, connection pooling, timeouts, retries
- Validate Configurations: Connection strings, retry policies, circuit breakers
- ORM/Driver Validation: Ensure NHibernate mappings, MongoDB drivers work correctly
- Transaction Behavior: Real transaction boundaries, rollback, isolation levels
- Message Bus Integration: End-to-end message publish/consume with real Service Bus
- Production Parity: Use same container images as production
- Regression Prevention: Catch breaking changes in dependencies
- Confidence: High confidence before deployment
- 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:
- Fundamentals: Philosophy, strategy, pyramid
- WebApplicationFactory & TestServer: In-process testing
- Test Configuration: Startup, fixtures, base classes
- Database Testing: SQL Server, MongoDB, Testcontainers
- Messaging Testing: MassTransit, Service Bus, Outbox/Inbox
- Cache Testing: Redis integration
- Storage Testing: Blob Storage (Azurite)
- API Testing: HTTP endpoints, authentication
- Cross-Service: Multi-service integration
- Docker Containers: Testcontainers, Azure Pipelines
- Test Data: Seeding, cleanup, builders
- Isolation: Strategies, parallel execution
- Multi-Tenant: RLS, tenant isolation testing
- Performance: Load testing integration
- CI/CD: Pipeline integration
- Operations: Troubleshooting, best practices
Related Documentation¶
- Testing Strategy: Overall testing approach
- Unit Testing: Component-level testing
- Acceptance Testing: BDD with Reqnroll
- CI/CD Pipeline: Service container configuration
- Runbook: Using integration tests for troubleshooting
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.