This implementation enhances the Blazor integration test infrastructure with two major capabilities:
- Editable In-Memory Test Data - A complete CRUD-enabled data store for managing test data during test execution
- OIDC Authentication Bypass - A test authentication handler that eliminates the need for Docker containers running OIDC services
Files Modified:
EstateManagementUI.BlazorServer/appsettings.json- AddedTestModesettingEstateManagementUI.BlazorServer/appsettings.Test.json- Created test-specific configuration withTestMode=true
The application now supports a "Test Mode" that can be enabled via configuration:
{
"AppSettings": {
"TestMode": true
}
}When enabled, the application uses test authentication and in-memory data storage instead of requiring OIDC and external data sources.
New File: EstateManagementUI.BlazorServer/Common/TestAuthenticationHandler.cs
Implements a custom authentication handler that:
- Bypasses OIDC authentication flow
- Automatically authenticates all requests
- Provides test user claims (user ID, name, email, estate ID, role)
- Eliminates dependency on SecurityService Docker containers
Key Features:
- Scheme Name: "TestAuthentication"
- Default Test User: "Test User" with estate ID
11111111-1111-1111-1111-111111111111 - Role: "Estate"
New Files:
EstateManagementUI.BlazorServer/Services/ITestDataStore.cs- InterfaceEstateManagementUI.BlazorServer/Services/TestDataStore.cs- Implementation
Provides a thread-safe, in-memory data store with full CRUD operations:
Estate Management:
GetEstate(Guid estateId)- Retrieve estate by IDSetEstate(EstateModel estate)- Set/update estate
Merchant Management:
GetMerchants(Guid estateId)- List all merchantsGetMerchant(Guid estateId, Guid merchantId)- Get specific merchantAddMerchant(Guid estateId, MerchantModel merchant)- Add new merchantUpdateMerchant(Guid estateId, MerchantModel merchant)- Update merchantRemoveMerchant(Guid estateId, Guid merchantId)- Remove merchant
Operator Management:
GetOperators(Guid estateId)- List all operatorsGetOperator(Guid estateId, Guid operatorId)- Get specific operatorAddOperator(Guid estateId, OperatorModel operator)- Add new operatorUpdateOperator(Guid estateId, OperatorModel operator)- Update operatorRemoveOperator(Guid estateId, Guid operatorId)- Remove operator
Contract Management:
GetContracts(Guid estateId)- List all contractsGetContract(Guid estateId, Guid contractId)- Get specific contractAddContract(Guid estateId, ContractModel contract)- Add new contractUpdateContract(Guid estateId, ContractModel contract)- Update contractRemoveContract(Guid estateId, Guid contractId)- Remove contract
Data Isolation:
Reset()- Clears all data and reinitializes with defaults
- Uses
ConcurrentDictionaryfor thread-safe operations - Data organized by Estate ID for isolation
- Includes default test data on initialization
- Supports multiple estates simultaneously
New File: EstateManagementUI.BlazorServer/Services/TestMediatorService.cs
Implements IMediator interface while using the in-memory test data store:
Query Handling:
- All entity queries (Estate, Merchant, Operator, Contract) read from
TestDataStore - Dashboard queries return mock data
- File processing queries return mock data
Command Handling:
- All CRUD commands execute against
TestDataStore - Data changes are persisted in memory for the test session
- Commands like
CreateMerchantCommand,UpdateOperatorCommand, etc. actually modify test data
Maintains Mediator Pattern:
- Fully compatible with existing application code
- No changes required to UI components or business logic
- Transparent swap-in for
StubbedMediatorService
Modified File: EstateManagementUI.BlazorServer/Program.cs
Updated to support conditional configuration based on test mode:
var testMode = builder.Configuration.GetValue<bool>("AppSettings:TestMode", false);
if (testMode)
{
// Test mode: Use test authentication and test mediator
builder.Services.AddAuthentication(...)
.AddScheme<..., TestAuthenticationHandler>(...);
builder.Services.AddSingleton<ITestDataStore, TestDataStore>();
builder.Services.AddSingleton<IMediator, TestMediatorService>();
}
else
{
// Production mode: Use OIDC and stubbed mediator
builder.Services.AddAuthentication(...)
.AddCookie(...)
.AddOpenIdConnect(...);
builder.Services.AddSingleton<IMediator, StubbedMediatorService>();
}New Files:
EstateManagementUI.BlazorIntegrationTests/Common/TestDataHelper.cs- Helper class for test data manipulationEstateManagementUI.BlazorIntegrationTests/TEST_INFRASTRUCTURE.md- Comprehensive documentation
TestDataHelper Features:
- Simplified API for common test operations
- Helper methods for creating test entities
- Easy access to default estate ID
- Reset functionality for test isolation
Example usage:
var helper = new TestDataHelper(testDataStore);
// Create test data
var merchant = helper.CreateTestMerchant(
helper.DefaultEstateId,
"Test Merchant",
"TEST001"
);
// Update test data
merchant.Balance = 10000;
helper.UpdateMerchant(helper.DefaultEstateId, merchant);
// Reset for next test
helper.Reset();The TestDataStore initializes with:
- ID:
11111111-1111-1111-1111-111111111111 - Name: "Test Estate"
- Test Merchant 1 (MERCH001) - £10,000 balance, Immediate settlement
- Test Merchant 2 (MERCH002) - £5,000 balance, Weekly settlement
- Test Merchant 3 (MERCH003) - £15,000 balance, Monthly settlement
- Safaricom - Requires custom merchant number
- Voucher - No special requirements
- Standard Transaction Contract - Mobile Topup with fees
- Voucher Sales Contract - Simple voucher product
- No Docker container startup time
- No OIDC service initialization
- Immediate test data availability
- Reduced infrastructure overhead
- No external dependencies
- No network calls to auth services
- Self-contained test execution
- Works in isolated environments
- Create custom test scenarios on-the-fly
- Modify data mid-test
- Verify data changes directly
- Easy test data setup and teardown
- Each test can reset data to default state
- Tests don't interfere with each other
- Predictable test data state
- Thread-safe operations
- Mediator pattern preserved - Core requirement met
- No changes to UI components
- No changes to business logic
- Drop-in replacement for existing services
[Given(@"I have added a new merchant")]
public void GivenIHaveAddedANewMerchant()
{
var merchant = new MerchantModel
{
MerchantId = Guid.NewGuid(),
MerchantName = "New Test Merchant",
MerchantReference = "NEWTEST001",
Balance = 0,
SettlementSchedule = "Immediate"
};
testDataHelper.AddMerchant(testDataHelper.DefaultEstateId, merchant);
}
[Then(@"the merchant should appear in the merchant list")]
public void ThenMerchantShouldAppear()
{
var merchants = testDataHelper.GetMerchants(testDataHelper.DefaultEstateId);
merchants.Should().Contain(m => m.MerchantReference == "NEWTEST001");
}[When(@"I update the merchant balance to (.*)")]
public void WhenIUpdateMerchantBalance(decimal newBalance)
{
var merchantId = Guid.Parse("22222222-2222-2222-2222-222222222222");
var merchant = testDataHelper.GetMerchant(testDataHelper.DefaultEstateId, merchantId);
merchant.Balance = newBalance;
testDataHelper.UpdateMerchant(testDataHelper.DefaultEstateId, merchant);
}
[Then(@"the merchant balance should be (.*)")]
public void ThenMerchantBalanceShouldBe(decimal expectedBalance)
{
var merchantId = Guid.Parse("22222222-2222-2222-2222-222222222222");
var merchant = testDataHelper.GetMerchant(testDataHelper.DefaultEstateId, merchantId);
merchant.Balance.Should().Be(expectedBalance);
}[BeforeScenario]
public void BeforeScenario()
{
// Reset to default state before each test
testDataHelper.Reset();
}
[AfterScenario]
public void AfterScenario()
{
// Optionally verify data state
var merchants = testDataHelper.GetMerchants(testDataHelper.DefaultEstateId);
Console.WriteLine($"Test ended with {merchants.Count} merchants");
}To migrate existing Docker-based tests:
-
Enable Test Mode
export AppSettings__TestMode=true -
Remove Docker Setup
- Remove SecurityService container setup
- Remove OIDC-related Docker configuration
- Keep only UI container if needed
-
Update Test Steps
- Replace API calls with
TestDataHelpermethods - Remove authentication setup steps
- Use in-memory data instead of database queries
- Replace API calls with
-
Add Data Reset
[BeforeScenario] public void ResetData() { testDataHelper.Reset(); }
- Configure Test Mode in appsettings.Test.json
- Inject TestDataHelper into step definitions
- Use TestDataHelper for all data operations
- Reset data between scenarios
- All data stores use
ConcurrentDictionary - Safe for parallel test execution
- No locking required in test code
- Data stored in memory for session duration
- Reset clears and reinitializes
- Garbage collected when test session ends
- Fast in-memory operations
- No I/O overhead
- No network latency
- Suitable for large test suites
- Original
StubbedMediatorServiceunchanged - Default behavior (TestMode=false) unchanged
- Existing tests continue to work
- No breaking changes to API
- Easy to extend with additional entities
- Can add more CRUD operations as needed
- Supports future data requirements
- Dashboard/File Data: Currently returns mock data, not integrated with test data store
- Transaction Data: Not yet implemented in test data store
- Complex Relationships: Some entity relationships simplified
- Persistence: Data only exists for test session duration
Potential improvements:
- Custom User Claims: Allow tests to specify different user contexts
- Data Fixtures: Pre-defined test data sets
- Data Snapshots: Save and restore specific data states
- Query Tracking: Record all queries for verification
- Command History: Log all commands for debugging
- Dashboard Integration: Integrate dashboard queries with test data
- Transaction Processing: Add transaction data to test store
This implementation successfully addresses the requirements:
✅ Editable Static Test Data - Complete CRUD operations with in-memory storage ✅ OIDC Bypass - Test authentication handler eliminates external dependencies ✅ Maintains Mediator Pattern - Core architectural pattern preserved ✅ Faster Tests - No Docker containers for authentication ✅ Simpler Setup - Configuration-based test mode ✅ Data Isolation - Reset functionality ensures test independence
The implementation provides a solid foundation for fast, flexible integration testing while maintaining the application's architectural integrity.