Config Platform - Testing Strategy Blueprint¶
Overview¶
This document provides concrete testing patterns and strategies for Config Platform services. Use these patterns to generate test code, test fixtures, and test automation pipelines.
Unit Testing Patterns¶
Domain Logic Testing¶
ConfigItem Aggregate Tests:
[TestClass]
public class ConfigItemTests
{
[TestMethod]
public void UpdateValue_ShouldIncrementVersion()
{
// Arrange
var configItem = new ConfigItem(
key: "api.url",
value: "https://api.example.com",
configSetId: Guid.NewGuid()
);
var initialVersion = configItem.Version;
// Act
configItem.UpdateValue("https://api.new.com", "user@example.com");
// Assert
Assert.AreEqual(initialVersion + 1, configItem.Version);
Assert.AreEqual("https://api.new.com", configItem.Value);
}
[TestMethod]
public void UpdateValue_WithInvalidValue_ShouldThrowException()
{
// Arrange
var configItem = new ConfigItem(
key: "api.url",
value: "https://api.example.com",
configSetId: Guid.NewGuid()
);
configItem.SetSchema(new ConfigSchema
{
JsonSchema = new { type = "string", format = "uri" }
});
// Act & Assert
Assert.ThrowsException<ValidationException>(() =>
configItem.UpdateValue("not-a-url", "user@example.com")
);
}
}
Repository Pattern Testing¶
[TestClass]
public class ConfigRepositoryTests
{
private IConfigRepository _repository;
private ISession _session;
[TestInitialize]
public void Setup()
{
var sessionFactory = CreateInMemorySessionFactory();
_session = sessionFactory.OpenSession();
_repository = new ConfigRepository(_session);
}
[TestMethod]
public async Task GetByPathAsync_ShouldReturnConfigItem()
{
// Arrange
var configItem = new ConfigItem(
key: "api.url",
value: "https://api.example.com",
configSetId: Guid.NewGuid()
);
await _repository.AddAsync(configItem);
await _session.FlushAsync();
// Act
var result = await _repository.GetByPathAsync("api.url", Guid.NewGuid());
// Assert
Assert.IsNotNull(result);
Assert.AreEqual("api.url", result.Key);
}
private ISessionFactory CreateInMemorySessionFactory()
{
var configuration = new Configuration();
configuration.DataBaseIntegration(db =>
{
db.ConnectionString = "Data Source=:memory:";
db.Dialect<SQLiteDialect>();
});
configuration.AddAssembly(typeof(ConfigItem).Assembly);
return configuration.BuildSessionFactory();
}
}
Service Layer Testing¶
[TestClass]
public class ConfigRegistryServiceTests
{
private Mock<IConfigRepository> _repositoryMock;
private Mock<IPolicyEngine> _policyEngineMock;
private Mock<ICache> _cacheMock;
private ConfigRegistryService _service;
[TestInitialize]
public void Setup()
{
_repositoryMock = new Mock<IConfigRepository>();
_policyEngineMock = new Mock<IPolicyEngine>();
_cacheMock = new Mock<ICache>();
_service = new ConfigRegistryService(
_repositoryMock.Object,
_policyEngineMock.Object,
_cacheMock.Object
);
}
[TestMethod]
public async Task ResolveAsync_CacheHit_ShouldReturnCachedValue()
{
// Arrange
var cachedConfig = new ResolvedConfig
{
Path = "api.url",
Value = "https://api.example.com",
ETag = "etag123"
};
_cacheMock.Setup(c => c.GetAsync<ResolvedConfig>(It.IsAny<string>()))
.ReturnsAsync(cachedConfig);
// Act
var result = await _service.ResolveAsync("api.url", new ResolutionContext
{
TenantId = Guid.NewGuid(),
ETag = "etag123"
});
// Assert
Assert.AreEqual(cachedConfig.Value, result.Value);
_repositoryMock.Verify(r => r.GetByPathAsync(It.IsAny<string>(), It.IsAny<Guid>()),
Times.Never);
}
}
Integration Testing Patterns¶
API Integration Tests¶
[TestClass]
public class ConfigApiIntegrationTests
{
private TestServer _server;
private HttpClient _client;
[TestInitialize]
public void Setup()
{
var builder = WebApplication.CreateBuilder();
builder.Services.AddConfigPlatform();
var app = builder.Build();
_server = new TestServer(new WebHostBuilder()
.UseStartup<TestStartup>());
_client = _server.CreateClient();
}
[TestMethod]
public async Task GetConfig_ShouldReturn200()
{
// Arrange
var token = await GetAuthTokenAsync();
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
// Act
var response = await _client.GetAsync("/api/v1/config-sets/test/items/api.url");
// Assert
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var config = JsonSerializer.Deserialize<ConfigItem>(content);
Assert.IsNotNull(config);
}
private async Task<string> GetAuthTokenAsync()
{
// Get OAuth2 token for testing
}
}
Database Integration Tests¶
[TestClass]
public class ConfigDatabaseIntegrationTests
{
private ISessionFactory _sessionFactory;
private ISession _session;
[TestInitialize]
public void Setup()
{
var configuration = new Configuration();
configuration.Configure();
configuration.AddAssembly(typeof(ConfigItem).Assembly);
_sessionFactory = configuration.BuildSessionFactory();
_session = _sessionFactory.OpenSession();
// Create test database schema
new SchemaExport(configuration).Execute(false, true, false, _session.Connection, null);
}
[TestMethod]
public async Task SaveConfigItem_ShouldPersistToDatabase()
{
// Arrange
var configItem = new ConfigItem(
key: "api.url",
value: "https://api.example.com",
configSetId: Guid.NewGuid()
);
// Act
await _session.SaveAsync(configItem);
await _session.FlushAsync();
_session.Clear();
// Assert
var saved = await _session.GetAsync<ConfigItem>(configItem.Id);
Assert.IsNotNull(saved);
Assert.AreEqual("api.url", saved.Key);
}
}
Contract Testing Patterns¶
API Contract Tests¶
[TestClass]
public class ConfigApiContractTests
{
[TestMethod]
public void ConfigItemResponse_ShouldMatchOpenApiSchema()
{
// Arrange
var response = new ConfigItemResponse
{
Id = Guid.NewGuid(),
Key = "api.url",
Value = "https://api.example.com",
Version = 1,
ETag = "etag123"
};
// Act
var json = JsonSerializer.Serialize(response);
var schema = LoadOpenApiSchema("ConfigItemResponse");
// Assert
var validationResult = JsonSchema.Validate(json, schema);
Assert.IsTrue(validationResult.IsValid,
string.Join(", ", validationResult.Errors));
}
}
Event Contract Tests¶
[TestClass]
public class ConfigEventContractTests
{
[TestMethod]
public void ConfigPublishedEvent_ShouldMatchCloudEventsSchema()
{
// Arrange
var @event = new ConfigPublishedEvent
{
Id = Guid.NewGuid().ToString(),
Type = "ecs.config.v1.ConfigPublished",
Source = "config-registry",
Time = DateTime.UtcNow,
Data = new
{
ConfigItemId = Guid.NewGuid(),
Path = "api.url",
Version = 1
}
};
// Act
var json = JsonSerializer.Serialize(@event);
var cloudEvent = CloudEvent.Parse(json);
// Assert
Assert.AreEqual("ecs.config.v1.ConfigPublished", cloudEvent.Type);
Assert.IsNotNull(cloudEvent.Data);
}
}
Performance Testing Patterns¶
Load Testing¶
[TestClass]
public class ConfigPerformanceTests
{
[TestMethod]
public async Task ResolveConfig_UnderLoad_ShouldMeetLatencyTarget()
{
// Arrange
var service = CreateConfigService();
var tasks = new List<Task<TimeSpan>>();
// Act
for (int i = 0; i < 1000; i++)
{
tasks.Add(MeasureResolveTimeAsync(service, $"api.url{i}"));
}
var results = await Task.WhenAll(tasks);
var p95 = CalculatePercentile(results, 95);
var p99 = CalculatePercentile(results, 99);
// Assert
Assert.IsTrue(p95.TotalMilliseconds < 50,
$"P95 latency {p95.TotalMilliseconds}ms exceeds 50ms target");
Assert.IsTrue(p99.TotalMilliseconds < 100,
$"P99 latency {p99.TotalMilliseconds}ms exceeds 100ms target");
}
private async Task<TimeSpan> MeasureResolveTimeAsync(
IConfigService service,
string path)
{
var stopwatch = Stopwatch.StartNew();
await service.ResolveAsync(path);
stopwatch.Stop();
return stopwatch.Elapsed;
}
}
Throughput Testing¶
[TestMethod]
public async Task ResolveConfig_ShouldHandleTargetThroughput()
{
// Arrange
var service = CreateConfigService();
var targetRPS = 1000;
var duration = TimeSpan.FromSeconds(10);
var startTime = DateTime.UtcNow;
var requestCount = 0;
// Act
while (DateTime.UtcNow - startTime < duration)
{
await service.ResolveAsync("api.url");
requestCount++;
}
var actualRPS = requestCount / duration.TotalSeconds;
// Assert
Assert.IsTrue(actualRPS >= targetRPS,
$"Actual RPS {actualRPS} below target {targetRPS}");
}
Test Data Setup Patterns¶
Test Fixture Builder¶
public class ConfigItemBuilder
{
private string _key = "api.url";
private object _value = "https://api.example.com";
private Guid _configSetId = Guid.NewGuid();
private bool _isSecret = false;
public ConfigItemBuilder WithKey(string key)
{
_key = key;
return this;
}
public ConfigItemBuilder WithValue(object value)
{
_value = value;
return this;
}
public ConfigItemBuilder AsSecret()
{
_isSecret = true;
return this;
}
public ConfigItem Build()
{
return new ConfigItem(_key, _value, _configSetId)
{
IsSecret = _isSecret
};
}
}
// Usage
var configItem = new ConfigItemBuilder()
.WithKey("payment.apiKey")
.AsSecret()
.Build();
Test Database Seeding¶
public class TestDataSeeder
{
private readonly ISession _session;
public async Task SeedAsync()
{
var configSet = new ConfigBundle
{
Id = Guid.NewGuid(),
Name = "test-config-set",
AppId = "test-app",
Environment = "test"
};
await _session.SaveAsync(configSet);
var configItem = new ConfigItem(
key: "api.url",
value: "https://api.test.com",
configSetId: configSet.Id
);
await _session.SaveAsync(configItem);
await _session.FlushAsync();
}
}
Mocking Strategies¶
Mock Policy Engine¶
public class MockPolicyEngine : IPolicyEngine
{
private readonly Dictionary<string, ValidationResult> _validationResults = new();
public void SetValidationResult(string path, ValidationResult result)
{
_validationResults[path] = result;
}
public Task<ValidationResult> ValidateAsync(ConfigDraft draft, ValidationContext context)
{
var key = $"{context.TenantId}:{draft.Path}";
if (_validationResults.TryGetValue(key, out var result))
{
return Task.FromResult(result);
}
return Task.FromResult(new ValidationResult { IsValid = true });
}
}
Mock Cache¶
public class InMemoryCache : ICache
{
private readonly Dictionary<string, object> _cache = new();
public Task<T> GetAsync<T>(string key)
{
if (_cache.TryGetValue(key, out var value))
{
return Task.FromResult((T)value);
}
return Task.FromResult<T>(default);
}
public Task SetAsync<T>(string key, T value, TimeSpan? expiration = null)
{
_cache[key] = value;
return Task.CompletedTask;
}
public Task RemoveAsync(string key)
{
_cache.Remove(key);
return Task.CompletedTask;
}
}
Test Organization¶
Test Project Structure¶
ConfigPlatform.Tests/
├── UnitTests/
│ ├── Domain/
│ │ ├── ConfigItemTests.cs
│ │ └── ConfigBundleTests.cs
│ ├── Application/
│ │ └── ConfigRegistryServiceTests.cs
│ └── Infrastructure/
│ └── ConfigRepositoryTests.cs
├── IntegrationTests/
│ ├── Api/
│ │ └── ConfigApiIntegrationTests.cs
│ └── Database/
│ └── ConfigDatabaseIntegrationTests.cs
├── ContractTests/
│ └── ApiContractTests.cs
├── PerformanceTests/
│ └── ConfigPerformanceTests.cs
└── TestHelpers/
├── ConfigItemBuilder.cs
└── TestDataSeeder.cs
References¶
- Solution Architecture - Testing requirements
- Domain Model Schema - Domain model for testing