Test Data Management in ConnectSoft Microservice Template¶
Purpose & Overview¶
Test Data Management is the systematic approach to creating, organizing, and maintaining test data for unit tests, integration tests, and acceptance tests. In the ConnectSoft Microservice Template, test data management follows patterns that ensure tests are isolated, maintainable, and reliable, providing consistent test data while avoiding test interdependencies.
Test data management provides:
- Test Isolation: Each test has its own data, preventing interference between tests
- Maintainability: Centralized test data creation reduces duplication
- Readability: Clear, expressive test data setup improves test comprehension
- Flexibility: Easy to create variations of test data for different scenarios
- Performance: Efficient data setup and cleanup strategies
- Reliability: Deterministic test data ensures consistent test results
- Reusability: Shared builders and factories reduce code duplication
Test Data Management Philosophy
Test data management is about creating the right data for the right test at the right time. The template emphasizes using builders and factories to create test data expressively, isolating tests with fresh data for each test run, and cleaning up data after tests to prevent interference. Test data should be as simple as possible while still being realistic enough to catch real-world issues.
Architecture Overview¶
Test Data Management Flow¶
Test Execution
↓
Test Setup (TestInitialize / Constructor)
├── Database Cleanup (if needed)
├── Test Data Creation
│ ├── Builders (Fluent API)
│ ├── Factories (Reusable Methods)
│ └── Seeders (Bulk Data)
└── Test Fixture Initialization
↓
Test Execution
├── Use Test Data
└── Create Additional Test Data (if needed)
↓
Test Cleanup (TestCleanup / Dispose)
├── Database Cleanup
├── Resource Disposal
└── State Reset
Test Data Management Components¶
Test Data Management
├── Builders
│ ├── Fluent API for Data Creation
│ ├── Default Values
│ └── Customization Methods
├── Factories
│ ├── Reusable Test Data Creation
│ ├── Scenario-Specific Data
│ └── Random Data Generation
├── Seeders
│ ├── Database Seeding
│ ├── Bulk Data Creation
│ └── Reference Data Setup
├── Fixtures
│ ├── Shared Test Setup
│ ├── Test Environment Configuration
│ └── Resource Management
└── Cleanup Strategies
├── Per-Test Cleanup
├── Per-Class Cleanup
├── Transaction Rollback
└── Database Truncation
Test Data Creation Patterns¶
Builder Pattern¶
Purpose: Fluent API for creating test data with default values and customization options.
Example:
public class MicroserviceAggregateRootEntityBuilder
{
private Guid objectId = Guid.NewGuid();
private string name = "Test Aggregate Root";
private DateTime createdAt = DateTime.UtcNow;
public MicroserviceAggregateRootEntityBuilder WithObjectId(Guid id)
{
this.objectId = id;
return this;
}
public MicroserviceAggregateRootEntityBuilder WithName(string name)
{
this.name = name;
return this;
}
public MicroserviceAggregateRootEntityBuilder WithCreatedAt(DateTime createdAt)
{
this.createdAt = createdAt;
return this;
}
public MicroserviceAggregateRootEntity Build()
{
return new MicroserviceAggregateRootEntity
{
ObjectId = this.objectId,
Name = this.name,
CreatedAt = this.createdAt
};
}
// Convenience methods for common scenarios
public static MicroserviceAggregateRootEntityBuilder Valid()
{
return new MicroserviceAggregateRootEntityBuilder();
}
public static MicroserviceAggregateRootEntityBuilder WithId(Guid id)
{
return new MicroserviceAggregateRootEntityBuilder().WithObjectId(id);
}
}
Usage:
// Basic usage
var entity = new MicroserviceAggregateRootEntityBuilder()
.WithName("My Test Entity")
.WithObjectId(Guid.Parse("11111111-1111-1111-1111-111111111111"))
.Build();
// Using convenience methods
var entity = MicroserviceAggregateRootEntityBuilder.Valid()
.WithName("Custom Name")
.Build();
// Default values (minimal setup)
var entity = new MicroserviceAggregateRootEntityBuilder().Build();
Benefits: - Fluent API: Readable and expressive - Default Values: Sensible defaults reduce boilerplate - Flexibility: Easy to customize for specific test scenarios - Reusability: Shared across multiple tests
Factory Pattern¶
Purpose: Reusable methods for creating test data with predefined scenarios.
Example:
public static class TestDataFactory
{
public static MicroserviceAggregateRootEntity CreateValidEntity()
{
return new MicroserviceAggregateRootEntity
{
ObjectId = Guid.NewGuid(),
Name = "Test Entity",
CreatedAt = DateTime.UtcNow
};
}
public static MicroserviceAggregateRootEntity CreateEntityWithId(Guid id)
{
return new MicroserviceAggregateRootEntity
{
ObjectId = id,
Name = $"Entity {id}",
CreatedAt = DateTime.UtcNow
};
}
public static CreateMicroserviceAggregateRootInput CreateValidInput()
{
return new CreateMicroserviceAggregateRootInput
{
ObjectId = Guid.NewGuid()
};
}
public static CreateMicroserviceAggregateRootRequest CreateValidRequest()
{
return new CreateMicroserviceAggregateRootRequest
{
ObjectId = Guid.NewGuid()
};
}
// Scenario-specific factories
public static MicroserviceAggregateRootEntity CreateDeletedEntity()
{
return new MicroserviceAggregateRootEntity
{
ObjectId = Guid.NewGuid(),
Name = "Deleted Entity",
CreatedAt = DateTime.UtcNow.AddDays(-1),
DeletedAt = DateTime.UtcNow
};
}
}
Usage:
// Simple factory method
var entity = TestDataFactory.CreateValidEntity();
// Scenario-specific factory
var deletedEntity = TestDataFactory.CreateDeletedEntity();
// Factory with parameters
var entity = TestDataFactory.CreateEntityWithId(testId);
Benefits: - Simplicity: Quick creation of common scenarios - Consistency: Same data structure across tests - Scenario-Specific: Predefined scenarios for common cases
Seeder Pattern¶
Purpose: Bulk data creation for integration tests and database seeding.
Example:
public static class DatabaseSeeder
{
public static async Task SeedTestDataAsync(
IMicroserviceAggregateRootsRepository repository)
{
var entities = new[]
{
new MicroserviceAggregateRootEntity
{
ObjectId = Guid.Parse("11111111-1111-1111-1111-111111111111"),
Name = "Test Entity 1",
CreatedAt = DateTime.UtcNow
},
new MicroserviceAggregateRootEntity
{
ObjectId = Guid.Parse("22222222-2222-2222-2222-222222222222"),
Name = "Test Entity 2",
CreatedAt = DateTime.UtcNow
},
new MicroserviceAggregateRootEntity
{
ObjectId = Guid.Parse("33333333-3333-3333-3333-333333333333"),
Name = "Test Entity 3",
CreatedAt = DateTime.UtcNow
}
};
foreach (var entity in entities)
{
await repository.InsertAsync(entity);
}
}
public static async Task SeedReferenceDataAsync(
IMicroserviceAggregateRootsRepository repository)
{
// Seed reference data (lookup tables, etc.)
var referenceEntities = new[]
{
new MicroserviceAggregateRootEntity
{
ObjectId = Guid.Parse("AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA"),
Name = "Reference Entity 1"
}
};
foreach (var entity in referenceEntities)
{
await repository.InsertAsync(entity);
}
}
}
Usage:
[TestInitialize]
public async Task Setup()
{
using var scope = this.server.Services.CreateScope();
var repository = scope.ServiceProvider
.GetRequiredService<IMicroserviceAggregateRootsRepository>();
await DatabaseSeeder.SeedTestDataAsync(repository);
}
Benefits: - Bulk Operations: Efficient for seeding multiple entities - Reference Data: Setup lookup tables and reference data - Consistency: Same seed data across multiple tests
Database Seeding¶
In-Memory Database Seeding¶
NHibernate In-Memory:
[TestClass]
public class RepositoryIntegrationTests
{
private ISessionFactory? sessionFactory;
private ISession? session;
[TestInitialize]
public void Setup()
{
// Configure in-memory SQLite database
var configuration = new Configuration()
.Configure()
.SetProperty(NHibernate.Cfg.Environment.ConnectionString,
"Data Source=:memory:;Version=3;New=True;")
.SetProperty(NHibernate.Cfg.Environment.Dialect,
typeof(SQLiteDialect).AssemblyQualifiedName);
// Add mappings
var modelMapper = new ModelMapper();
modelMapper.AddMapping<MicroserviceAggregateRootEntityMap>();
configuration.AddMapping(modelMapper.CompileMappingForAllExplicitlyAddedEntities());
this.sessionFactory = configuration.BuildSessionFactory();
this.session = this.sessionFactory.OpenSession();
// Create schema
var schemaExport = new SchemaExport(configuration);
schemaExport.Execute(true, true, false, this.session.Connection, null);
// Seed test data
this.SeedTestData();
}
private void SeedTestData()
{
var repository = new MicroserviceAggregateRootsRepository(this.session!);
var entities = new[]
{
new MicroserviceAggregateRootEntity
{
ObjectId = Guid.NewGuid(),
Name = "Test Entity 1"
},
new MicroserviceAggregateRootEntity
{
ObjectId = Guid.NewGuid(),
Name = "Test Entity 2"
}
};
foreach (var entity in entities)
{
repository.Insert(entity);
}
this.session!.Flush();
}
[TestCleanup]
public void Cleanup()
{
this.session?.Dispose();
this.sessionFactory?.Dispose();
}
}
Test Container Database Seeding¶
Using Testcontainers:
[TestClass]
public class DatabaseIntegrationTests : IAsyncLifetime
{
private SqlServerContainer? sqlServerContainer;
private IServiceProvider? serviceProvider;
public async Task InitializeAsync()
{
// Start SQL Server container
this.sqlServerContainer = new SqlServerBuilder()
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
.WithPassword("Test123!")
.Build();
await this.sqlServerContainer.StartAsync();
// Configure services with container connection string
var services = new ServiceCollection();
var connectionString = this.sqlServerContainer.GetConnectionString();
// Configure NHibernate with container connection string
services.AddNHibernatePersistenceModel(connectionString);
this.serviceProvider = services.BuildServiceProvider();
// Run migrations
var migrationRunner = this.serviceProvider
.GetRequiredService<MigrationRunner>();
migrationRunner.Up();
// Seed test data
await this.SeedTestDataAsync();
}
private async Task SeedTestDataAsync()
{
using var scope = this.serviceProvider!.CreateScope();
var repository = scope.ServiceProvider
.GetRequiredService<IMicroserviceAggregateRootsRepository>();
await DatabaseSeeder.SeedTestDataAsync(repository);
}
public async Task DisposeAsync()
{
await this.sqlServerContainer?.StopAsync()!;
}
}
MongoDB Seeding¶
MongoDB Test Database:
[TestClass]
public class MongoDbIntegrationTests
{
private MongoClient? mongoClient;
private IServiceProvider? serviceProvider;
private const string DbName = "TestDatabase";
[ClassInitialize]
public static void ClassInitialize(TestContext context)
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.Test.json")
.Build();
var services = new ServiceCollection();
services.AddLogging();
mongoClient = new MongoClient(
configuration.GetConnectionString("MongoDb"));
// Drop database if exists
var foundName = mongoClient
.ListDatabaseNames()
.ToList()
.FirstOrDefault(s => string.Equals(s, DbName, StringComparison.Ordinal));
if (!string.IsNullOrEmpty(foundName))
{
mongoClient.DropDatabase(DbName);
}
services.UseMongoDbPersistence(configuration, DbName, DbName);
services.AddScoped<IMicroserviceAggregateRootsRepository, MicroserviceAggregateRootsRepository>();
serviceProvider = services.BuildServiceProvider();
// Run migrations
var migrationRunner = serviceProvider
.GetRequiredService<MigrationRunner>();
migrationRunner.Up();
}
[TestInitialize]
public async Task Setup()
{
// Seed test data for each test
using var scope = serviceProvider!.CreateScope();
var repository = scope.ServiceProvider
.GetRequiredService<IMicroserviceAggregateRootsRepository>();
await DatabaseSeeder.SeedTestDataAsync(repository);
}
[ClassCleanup]
public static void ClassCleanup()
{
serviceProvider?.GetRequiredService<MigrationRunner>().Up(version: 0);
mongoClient?.DropDatabase(DbName);
}
}
Test Isolation¶
Per-Test Isolation¶
Fresh Data for Each Test:
[TestClass]
public class ControllerTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> factory;
[TestInitialize]
public async Task Setup()
{
// Clear database before each test
await this.ClearDatabaseAsync();
// Seed fresh test data
await this.SeedTestDataAsync();
}
[TestCleanup]
public async Task Cleanup()
{
// Clean up test-specific data
await this.CleanupTestDataAsync();
}
private async Task ClearDatabaseAsync()
{
var client = this.factory.CreateClient();
var server = this.factory.Server;
using var scope = server.Services.CreateScope();
var repository = scope.ServiceProvider
.GetRequiredService<IMicroserviceAggregateRootsRepository>();
// Clear all test data
var allEntities = await repository.GetAllAsync();
foreach (var entity in allEntities)
{
await repository.DeleteAsync(entity.ObjectId);
}
}
private async Task SeedTestDataAsync()
{
var server = this.factory.Server;
using var scope = server.Services.CreateScope();
var repository = scope.ServiceProvider
.GetRequiredService<IMicroserviceAggregateRootsRepository>();
await DatabaseSeeder.SeedTestDataAsync(repository);
}
private async Task CleanupTestDataAsync()
{
// Clean up any test-specific data created during test
}
}
Transaction-Based Isolation¶
Rollback Transactions After Each Test:
[TestClass]
public class RepositoryTests
{
private ISession? session;
private ITransaction? transaction;
[TestInitialize]
public void Setup()
{
this.session = this.sessionFactory.OpenSession();
this.transaction = this.session.BeginTransaction();
// Seed test data within transaction
this.SeedTestData();
}
[TestCleanup]
public void Cleanup()
{
// Rollback transaction to undo all changes
this.transaction?.Rollback();
this.transaction?.Dispose();
this.session?.Dispose();
}
[TestMethod]
public async Task Repository_Should_Save_Entity()
{
// Test within transaction
var repository = new MicroserviceAggregateRootsRepository(this.session!);
var entity = TestDataFactory.CreateValidEntity();
await repository.InsertAsync(entity);
await this.session!.FlushAsync();
// Assertions
var retrieved = await repository.GetByIdAsync(entity.ObjectId);
Assert.IsNotNull(retrieved);
// Transaction will be rolled back in cleanup
}
}
Unique Database Per Test¶
Isolated Database Instances:
[TestClass]
public class IsolatedDatabaseTests
{
private string testDatabaseName;
[TestInitialize]
public void Setup()
{
// Create unique database name for this test
this.testDatabaseName = $"TestDb_{Guid.NewGuid():N}";
// Create and configure database
this.CreateTestDatabase(this.testDatabaseName);
this.SeedTestData();
}
[TestCleanup]
public void Cleanup()
{
// Drop test database
this.DropTestDatabase(this.testDatabaseName);
}
private void CreateTestDatabase(string dbName)
{
// Create database with unique name
// Configure NHibernate/MongoDB with this database
}
private void DropTestDatabase(string dbName)
{
// Drop the test database
}
}
Test Fixtures¶
Base Test Fixture¶
Shared Setup and Cleanup:
public abstract class IntegrationTestBase
{
protected WebApplicationFactory<Program> 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()
{
this.Factory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Override services for testing
});
});
this.Client = this.Factory.CreateClient();
this.Services = this.Factory.Services;
// Set default test headers
this.Client.DefaultRequestHeaders.Add("X-Test-Tenant-Id", this.TestTenantId);
this.Client.DefaultRequestHeaders.Add("X-Test-User-Id", "test-user");
// Seed test data
this.SeedTestData();
}
[TestCleanup]
public virtual void TestCleanup()
{
// Clean up test data
this.CleanupTestData();
this.Client?.Dispose();
this.Factory?.Dispose();
}
protected abstract void SeedTestData();
protected abstract void CleanupTestData();
}
// Usage
[TestClass]
public class ControllerTests : IntegrationTestBase
{
protected override void SeedTestData()
{
using var scope = this.Services.CreateScope();
var repository = scope.ServiceProvider
.GetRequiredService<IMicroserviceAggregateRootsRepository>();
// Seed test data
var entity = TestDataFactory.CreateValidEntity();
repository.Insert(entity);
}
protected override void CleanupTestData()
{
using var scope = this.Services.CreateScope();
var repository = scope.ServiceProvider
.GetRequiredService<IMicroserviceAggregateRootsRepository>();
// Clean up test data
var allEntities = repository.GetAll();
foreach (var entity in allEntities)
{
repository.Delete(entity.ObjectId);
}
}
[TestMethod]
public async Task Get_Should_Return_Ok()
{
var response = await this.Client.GetAsync("/api/aggregateroots");
response.EnsureSuccessStatusCode();
}
}
Processor Test Fixture¶
Domain-Specific Test Fixtures:
public class ProcessorTestFixture
{
public Mock<IMicroserviceAggregateRootsRepository> MockRepository { get; }
public Mock<IUnitOfWork> MockUnitOfWork { get; }
public Mock<IEventBus> MockEventBus { get; }
public DefaultMicroserviceAggregateRootsProcessor Processor { get; }
public ProcessorTestFixture()
{
this.MockRepository = new Mock<IMicroserviceAggregateRootsRepository>();
this.MockUnitOfWork = new Mock<IUnitOfWork>();
this.MockEventBus = new Mock<IEventBus>();
this.Processor = new DefaultMicroserviceAggregateRootsProcessor(
Mock.Of<ILogger<DefaultMicroserviceAggregateRootsProcessor>>(),
TimeProvider.System,
this.MockRepository.Object,
this.MockUnitOfWork.Object,
this.MockEventBus.Object,
Mock.Of<IValidator<CreateMicroserviceAggregateRootInput>>(),
Mock.Of<IValidator<DeleteMicroserviceAggregateRootInput>>(),
Mock.Of<MicroserviceTemplateMetrics>());
}
public void SetupEntityExists(Guid id)
{
this.MockRepository.Setup(r => r.GetByIdAsync(id))
.ReturnsAsync(new MicroserviceAggregateRootEntity
{
ObjectId = id
});
}
public void SetupEntityNotFound(Guid id)
{
this.MockRepository.Setup(r => r.GetByIdAsync(id))
.ReturnsAsync((IMicroserviceAggregateRoot?)null);
}
}
// Usage
[TestClass]
public class ProcessorTests
{
private ProcessorTestFixture fixture;
[TestInitialize]
public void Setup()
{
this.fixture = new ProcessorTestFixture();
}
[TestMethod]
public async Task Create_WithValidInput_Should_Succeed()
{
// Arrange
var input = TestDataFactory.CreateValidInput();
this.fixture.SetupEntityNotFound(input.ObjectId);
// Act
var result = await this.fixture.Processor
.CreateMicroserviceAggregateRoot(input);
// Assert
Assert.IsNotNull(result);
this.fixture.MockRepository.Verify(
r => r.Insert(It.IsAny<IMicroserviceAggregateRoot>()),
Times.Once);
}
}
Cleanup Strategies¶
Database Cleanup in TestStartup¶
Cleanup Before Test Suite:
public class TestStartup : Startup
{
public override void ConfigureServices(IServiceCollection services)
{
#if UseNHibernate
// Drop database if exists before starting tests
this.DropSqlDatabase(TestConstants.DbName);
#endif
#if UseMongoDb
// Drop MongoDB database if exists
this.DropMongoDbDatabase(TestConstants.DbName);
#endif
base.ConfigureServices(services);
}
#if UseNHibernate
private void DropSqlDatabase(string dbName)
{
var connectionString = this.Configuration
.GetConnectionString(TestConstants.NHibernateConnectionStringKey);
if (!IsDatabaseExists(connectionString, dbName))
{
return;
}
new SqlServerDatabaseHelper().ExecuteSql(
connectionString,
$"Use master;ALTER DATABASE [{dbName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;DROP DATABASE [{dbName}];");
}
#endif
#if UseMongoDb
private void DropMongoDbDatabase(string dbName)
{
var mongoClient = new MongoClient(
this.Configuration.GetConnectionString(TestConstants.MongoDbConnectionStringKey));
var foundName = mongoClient.ListDatabaseNames()
.ToList()
.FirstOrDefault(s => string.Equals(s, dbName, StringComparison.Ordinal));
if (!string.IsNullOrEmpty(foundName))
{
mongoClient.DropDatabase(dbName);
}
}
#endif
}
Per-Class Cleanup¶
Cleanup After All Tests in Class:
[TestClass]
public class DatabaseTests
{
private static IServiceProvider? serviceProvider;
private static MongoClient? mongoClient;
private const string DbName = "TestDatabase";
[ClassInitialize]
public static void ClassInitialize(TestContext context)
{
// Setup database and services
// ...
serviceProvider = services.BuildServiceProvider();
}
[ClassCleanup]
public static void ClassCleanup()
{
// Clean up database after all tests
serviceProvider?.GetRequiredService<MigrationRunner>().Up(version: 0);
mongoClient?.DropDatabase(DbName);
}
[TestInitialize]
public async Task Setup()
{
// Seed test data for each test
}
[TestCleanup]
public async Task Cleanup()
{
// Clean up test-specific data
}
}
Best Practices¶
Do's¶
-
Use Builders for Complex Test Data
-
Provide Sensible Defaults
-
Isolate Tests with Fresh Data
-
Use Factories for Common Scenarios
-
Clean Up After Tests
-
Use Unique Identifiers
Don'ts¶
-
Don't Share State Between Tests
-
Don't Hardcode Test Data
-
Don't Skip Cleanup
-
Don't Use Production Data
-
Don't Create Unnecessary Dependencies
-
Don't Ignore Test Isolation
// ❌ BAD - Tests depend on execution order [TestMethod] public void Test1() { /* Creates entity with ID 1 */ } [TestMethod] public void Test2() { /* Expects entity with ID 1 */ } // ✅ GOOD - Each test is independent [TestMethod] public void Test1() { var entity = TestDataFactory.CreateEntityWithId(id1); } [TestMethod] public void Test2() { var entity = TestDataFactory.CreateEntityWithId(id2); }
Common Scenarios¶
Scenario 1: Unit Test with Mock Data¶
Setup:
[TestMethod]
public async Task Processor_WithValidInput_Should_Succeed()
{
// Arrange
var mockRepository = new Mock<IMicroserviceAggregateRootsRepository>();
var input = TestDataFactory.CreateValidInput();
mockRepository.Setup(r => r.GetByIdAsync(input.ObjectId))
.ReturnsAsync((IMicroserviceAggregateRoot?)null);
var processor = new DefaultMicroserviceAggregateRootsProcessor(
/* dependencies */);
// Act
var result = await processor.CreateMicroserviceAggregateRoot(input);
// Assert
Assert.IsNotNull(result);
}
Scenario 2: Integration Test with Database¶
Setup:
[TestClass]
public class RepositoryIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> factory;
[TestInitialize]
public async Task Setup()
{
var server = this.factory.Server;
using var scope = server.Services.CreateScope();
var repository = scope.ServiceProvider
.GetRequiredService<IMicroserviceAggregateRootsRepository>();
// Clear and seed test data
await this.ClearDatabaseAsync(repository);
await DatabaseSeeder.SeedTestDataAsync(repository);
}
[TestMethod]
public async Task Get_WithSeededData_Should_Return_Entities()
{
var client = this.factory.CreateClient();
var response = await client.GetAsync("/api/aggregateroots");
response.EnsureSuccessStatusCode();
var entities = await response.Content.ReadFromJsonAsync<List<EntityDto>>();
Assert.IsTrue(entities!.Count > 0);
}
}
Scenario 3: BDD Test with Scenario Data¶
Setup:
[Binding]
public class FeatureSteps
{
private ScenarioContext scenarioContext;
private WebApplicationFactory<Program> factory;
[Given(@"I have a valid aggregate root")]
public void GivenIHaveAValidAggregateRoot()
{
var entity = TestDataFactory.CreateValidEntity();
this.scenarioContext["Entity"] = entity;
}
[Given(@"I have seeded test data")]
public async Task GivenIHaveSeededTestData()
{
var server = this.factory.Server;
using var scope = server.Services.CreateScope();
var repository = scope.ServiceProvider
.GetRequiredService<IMicroserviceAggregateRootsRepository>();
await DatabaseSeeder.SeedTestDataAsync(repository);
}
}
Troubleshooting¶
Issue: Tests Interfere with Each Other¶
Symptoms: Tests pass individually but fail when run together.
Solutions: 1. Ensure Test Isolation:
[TestInitialize]
public async Task Setup()
{
await this.ClearDatabaseAsync();
await this.SeedTestDataAsync();
}
-
Use Unique Identifiers:
-
Use Transactions with Rollback:
Issue: Database State Persists Between Test Runs¶
Symptoms: Old test data appears in new test runs.
Solutions: 1. Clean Database in TestStartup:
public override void ConfigureServices(IServiceCollection services)
{
this.DropSqlDatabase(TestConstants.DbName);
base.ConfigureServices(services);
}
- Use ClassCleanup:
Issue: Test Data Setup is Slow¶
Symptoms: Tests take too long to run.
Solutions: 1. Use In-Memory Databases:
-
Seed Only Required Data:
-
Use Test Fixtures for Shared Setup:
Issue: Test Data Not Found¶
Symptoms: Tests fail with "entity not found" errors.
Solutions: 1. Verify Seeding Completed:
await DatabaseSeeder.SeedTestDataAsync(repository);
await repository.FlushAsync(); // Ensure data is persisted
-
Check Transaction Scope:
-
Use Explicit IDs:
Related Documentation¶
- Unit Testing: Unit testing patterns and strategies
- Integration Testing: Integration testing with database
- Mocking Strategies: Mocking and test doubles
- BDD Testing: Behavior-driven development testing
Summary¶
Test data management in the ConnectSoft Microservice Template provides:
- ✅ Builder Pattern: Fluent API for creating test data
- ✅ Factory Pattern: Reusable test data creation methods
- ✅ Seeder Pattern: Bulk data seeding for integration tests
- ✅ Test Isolation: Fresh data for each test
- ✅ Cleanup Strategies: Proper resource cleanup
- ✅ Test Fixtures: Shared setup and configuration
- ✅ Best Practices: Patterns for maintainable test data
By following these patterns, teams can:
- Create Reliable Tests: Isolated tests that don't interfere with each other
- Improve Maintainability: Centralized test data creation reduces duplication
- Enhance Readability: Clear, expressive test data setup
- Ensure Performance: Efficient data setup and cleanup
- Enable Reusability: Shared builders and factories across tests
Test data management is essential for creating robust, maintainable test suites that provide confidence in code correctness while remaining fast and reliable.