Skip to content

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