USSD Internals
USSD Integration Testing Guide
This document provides a comprehensive guide for designing and implementing integration tests for USSD applications built with the Tech231 Platform DSL.
Overview
Integration tests for USSD applications verify the end-to-end behavior of DSL-defined applications, including:
- Session lifecycle (start, input, terminate)
- Menu navigation and state transitions
- Action execution (HTTP calls, variable storage)
- Global navigation (Back, Home)
- Error handling and edge cases
The tests use Orleans TestCluster for in-memory grain activation without external dependencies.
Test Architecture
Code┌─────────────────────────────────────────────────────────────────────┐ │ Test Project Structure │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ Tech231.Platform.Ussd.Integration.Tests/ │ │ ├── Fixtures/ │ │ │ └── OrleansTestFixture.cs (TestCluster setup) │ │ ├── Builders/ │ │ │ └── ApplicationDefinitionBuilder.cs (DSL test data) │ │ ├── Mocks/ │ │ │ └── MockHttpClientFactory.cs (HTTP action mocking) │ │ ├── BankingApp/ │ │ │ ├── BankingAppTests.cs (Full flow tests) │ │ │ ├── BalanceCheckTests.cs (Feature-specific) │ │ │ ├── TransferTests.cs (Feature-specific) │ │ │ └── NavigationTests.cs (Back/Home tests) │ │ └── Infrastructure/ │ │ └── SessionTestHelper.cs (Test utilities) │ │ │ └─────────────────────────────────────────────────────────────────────┘
Orleans TestCluster Setup
Test Fixture
Code// Fixtures/OrleansTestFixture.cs using Microsoft.Extensions.DependencyInjection; using Orleans.TestingHost; using Tech231.Platform.Ussd.Domain.Dsl; using Tech231.Platform.Ussd.Domain.Templates; using Tech231.Platform.Ussd.Orleans.Services; namespace Tech231.Platform.Ussd.Integration.Tests.Fixtures; /// <summary> /// Shared Orleans TestCluster fixture for integration tests. /// </summary> public class OrleansTestFixture : IAsyncLifetime { public TestCluster Cluster { get; private set; } = null!; public IClusterClient Client => Cluster.Client; // Test tenant for isolation public const string TestTenantId = "test-tenant-001"; public async Task InitializeAsync() { var builder = new TestClusterBuilder(); builder.AddSiloBuilderConfigurator<TestSiloConfigurator>(); Cluster = builder.Build(); await Cluster.DeployAsync(); } public async Task DisposeAsync() { await Cluster.StopAllSilosAsync(); } private class TestSiloConfigurator : ISiloConfigurator { public void Configure(ISiloBuilder siloBuilder) { siloBuilder .AddMemoryGrainStorage("ussd") .AddMemoryGrainStorage("applications") .ConfigureServices(services => { // Register template engine services.AddSingleton<ITemplateEngine, HandlebarsTemplateEngine>(); // Register mock HTTP client for action execution services.AddSingleton<IHttpClientFactory, MockHttpClientFactory>(); services.AddSingleton<IActionExecutor, ActionExecutor>(); }); } } }
Collection Definition
Code// Fixtures/OrleansTestCollection.cs [CollectionDefinition("Orleans")] public class OrleansTestCollection : ICollectionFixture<OrleansTestFixture> { // This class has no code, it's just a marker for xUnit }
Test Data Builders
Application Definition Builder
Code// Builders/ApplicationDefinitionBuilder.cs using Tech231.Platform.Ussd.Domain.Dsl; namespace Tech231.Platform.Ussd.Integration.Tests.Builders; /// <summary> /// Fluent builder for creating test USSD application definitions. /// </summary> public class ApplicationDefinitionBuilder { private string _name = "test-app"; private string _startBlock = "MainMenu"; private readonly List<MenuBlock> _menus = new(); private readonly List<ActionBlock> _actions = new(); private ApplicationOptions? _options; public ApplicationDefinitionBuilder WithName(string name) { _name = name; return this; } public ApplicationDefinitionBuilder WithStartBlock(string startBlock) { _startBlock = startBlock; return this; } public ApplicationDefinitionBuilder WithGlobalActions(string? backKey = null, string? homeKey = null) { _options = new ApplicationOptions { GlobalActions = new GlobalActionsConfig { BackKey = backKey, HomeKey = homeKey } }; return this; } public ApplicationDefinitionBuilder WithMenu(MenuBlock menu) { _menus.Add(menu); return this; } public ApplicationDefinitionBuilder WithMenu( string name, string text, bool terminal = false, params MenuOption[] options) { _menus.Add(new MenuBlock { Name = name, Text = text, Terminal = terminal, Options = options.ToList() }); return this; } public ApplicationDefinitionBuilder WithAction(ActionBlock action) { _actions.Add(action); return this; } public ApplicationDefinition Build() { return new ApplicationDefinition { Name = _name, StartBlock = _startBlock, Menus = _menus, Actions = _actions, Options = _options }; } } /// <summary> /// Pre-built test applications for common scenarios. /// </summary> public static class TestApplications { /// <summary> /// Simple banking application for integration tests. /// </summary> public static ApplicationDefinition BankingApp() => new ApplicationDefinitionBuilder() .WithName("banking-app") .WithStartBlock("MainMenu") .WithGlobalActions(backKey: "0", homeKey: "00") .WithMenu("MainMenu", "Welcome to Banking App\n1. Check Balance\n2. Transfer\n3. Airtime", terminal: false, new MenuOption { Key = "1", Goto = "BalanceDisplay" }, new MenuOption { Key = "2", Goto = "TransferMenu" }, new MenuOption { Key = "3", Goto = "AirtimeMenu" }) .WithMenu("BalanceDisplay", "Your balance is ${{session.data.balance}}", terminal: true) .WithMenu("TransferMenu", "Enter recipient number:", terminal: false, new MenuOption { Key = "*", Goto = "TransferAmount", Actions = new List<OptionAction> { new() { Type = "set", Key = "recipient", Value = "{{session.input}}" } } }) .WithMenu("TransferAmount", "Enter amount for {{session.data.recipient}}:", terminal: false, new MenuOption { Key = "*", Goto = "TransferConfirm", Actions = new List<OptionAction> { new() { Type = "set", Key = "amount", Value = "{{session.input}}" } } }) .WithMenu("TransferConfirm", "Transfer ${{session.data.amount}} to {{session.data.recipient}}?\n1. Confirm\n2. Cancel", terminal: false, new MenuOption { Key = "1", Goto = "ExecuteTransfer" }, new MenuOption { Key = "2", Goto = "MainMenu" }) .WithAction(new ActionBlock { Name = "ExecuteTransfer", Type = "http", Method = "POST", Url = "{{env.BANKING_API}}/transfers", Next = "TransferSuccess", Error = "TransferFailed" }) .WithMenu("TransferSuccess", "Transfer successful!\nRef: {{response.transactionId}}", terminal: true) .WithMenu("TransferFailed", "Transfer failed: {{response.error}}", terminal: true) .WithMenu("AirtimeMenu", "Select amount:\n1. $5\n2. $10\n3. $20", terminal: false, new MenuOption { Key = "1", Goto = "BuyAirtime", Actions = new() { new() { Type = "set", Key = "amount", Value = "5" } } }, new MenuOption { Key = "2", Goto = "BuyAirtime", Actions = new() { new() { Type = "set", Key = "amount", Value = "10" } } }, new MenuOption { Key = "3", Goto = "BuyAirtime", Actions = new() { new() { Type = "set", Key = "amount", Value = "20" } } }) .Build(); }
Mock HTTP Client
Code// Mocks/MockHttpClientFactory.cs using System.Net; using System.Text.Json; namespace Tech231.Platform.Ussd.Integration.Tests.Mocks; /// <summary> /// Mock HTTP client factory for testing action execution. /// </summary> public class MockHttpClientFactory : IHttpClientFactory { private readonly Dictionary<string, MockResponse> _responses = new(); public void SetupResponse(string urlPattern, object responseBody, HttpStatusCode statusCode = HttpStatusCode.OK) { _responses[urlPattern] = new MockResponse(responseBody, statusCode); } public void SetupError(string urlPattern, string errorMessage, HttpStatusCode statusCode = HttpStatusCode.InternalServerError) { _responses[urlPattern] = new MockResponse(new { error = errorMessage }, statusCode); } public HttpClient CreateClient(string name) { return new HttpClient(new MockHttpMessageHandler(_responses)); } private record MockResponse(object Body, HttpStatusCode StatusCode); private class MockHttpMessageHandler : HttpMessageHandler { private readonly Dictionary<string, MockResponse> _responses; public MockHttpMessageHandler(Dictionary<string, MockResponse> responses) { _responses = responses; } protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { var url = request.RequestUri?.ToString() ?? ""; foreach (var (pattern, response) in _responses) { if (url.Contains(pattern)) { return Task.FromResult(new HttpResponseMessage(response.StatusCode) { Content = new StringContent(JsonSerializer.Serialize(response.Body)) }); } } // Default: return 404 for unmocked endpoints return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound) { Content = new StringContent(JsonSerializer.Serialize(new { error = "Endpoint not mocked" })) }); } } }
Banking Application Tests
Full Flow Tests
Code// BankingApp/BankingAppTests.cs using Tech231.Platform.Ussd.Integration.Tests.Builders; using Tech231.Platform.Ussd.Integration.Tests.Fixtures; using Tech231.Platform.Ussd.Orleans.Interfaces; using Tech231.Platform.Ussd.Domain.Utilities; namespace Tech231.Platform.Ussd.Integration.Tests.BankingApp; [Collection("Orleans")] public class BankingAppTests { private readonly OrleansTestFixture _fixture; private readonly string _tenantId = OrleansTestFixture.TestTenantId; public BankingAppTests(OrleansTestFixture fixture) { _fixture = fixture; } [Fact] public async Task StartSession_ShowsMainMenu() { // Arrange var sessionId = $"session-{Guid.NewGuid()}"; var appDefinition = TestApplications.BankingApp(); await DeployApplication(appDefinition); var sessionGrain = _fixture.Client.GetGrain<IUSSDSessionGrain>( UssdGrainKey.ForSession(_tenantId, sessionId)); // Act var response = await sessionGrain.StartSessionAsync(new SessionStartRequest( ApplicationName: appDefinition.Name, Msisdn: "231881234567", ShortCode: "*123#")); // Assert Assert.Equal(UssdResponseType.Continue, response.ResponseType); Assert.Equal("MainMenu", response.CurrentMenu); Assert.Contains("Welcome to Banking App", response.Text); Assert.Contains("1. Check Balance", response.Text); } [Fact] public async Task CheckBalance_NavigatesToTerminalMenu() { // Arrange var sessionId = $"session-{Guid.NewGuid()}"; var appDefinition = TestApplications.BankingApp(); await DeployApplication(appDefinition); var sessionGrain = _fixture.Client.GetGrain<IUSSDSessionGrain>( UssdGrainKey.ForSession(_tenantId, sessionId)); await sessionGrain.StartSessionAsync(new SessionStartRequest( ApplicationName: appDefinition.Name, Msisdn: "231881234567", ShortCode: "*123#")); // Act var response = await sessionGrain.ProcessInputAsync("1"); // Assert Assert.Equal(UssdResponseType.End, response.ResponseType); Assert.Equal("BalanceDisplay", response.CurrentMenu); Assert.Contains("balance", response.Text.ToLower()); } [Fact] public async Task TransferFlow_CompletesSuccessfully() { // Arrange var sessionId = $"session-{Guid.NewGuid()}"; var appDefinition = TestApplications.BankingApp(); await DeployApplication(appDefinition); var mockHttp = GetMockHttpClientFactory(); mockHttp.SetupResponse("/transfers", new { transactionId = "TXN-123456", newBalance = "100.00" }); var sessionGrain = _fixture.Client.GetGrain<IUSSDSessionGrain>( UssdGrainKey.ForSession(_tenantId, sessionId)); // Act - Start session await sessionGrain.StartSessionAsync(new SessionStartRequest( ApplicationName: appDefinition.Name, Msisdn: "231881234567", ShortCode: "*123#")); // Act - Select Transfer (option 2) var response1 = await sessionGrain.ProcessInputAsync("2"); Assert.Equal("TransferMenu", response1.CurrentMenu); // Act - Enter recipient var response2 = await sessionGrain.ProcessInputAsync("231887654321"); Assert.Equal("TransferAmount", response2.CurrentMenu); // Act - Enter amount var response3 = await sessionGrain.ProcessInputAsync("50"); Assert.Equal("TransferConfirm", response3.CurrentMenu); // Act - Confirm transfer var response4 = await sessionGrain.ProcessInputAsync("1"); // Assert Assert.Equal(UssdResponseType.End, response4.ResponseType); Assert.Equal("TransferSuccess", response4.CurrentMenu); Assert.Contains("TXN-123456", response4.Text); } [Fact] public async Task TransferFlow_Cancelled_ReturnsToMainMenu() { // Arrange var sessionId = $"session-{Guid.NewGuid()}"; var appDefinition = TestApplications.BankingApp(); await DeployApplication(appDefinition); var sessionGrain = _fixture.Client.GetGrain<IUSSDSessionGrain>( UssdGrainKey.ForSession(_tenantId, sessionId)); await sessionGrain.StartSessionAsync(new SessionStartRequest( ApplicationName: appDefinition.Name, Msisdn: "231881234567", ShortCode: "*123#")); // Navigate to TransferConfirm await sessionGrain.ProcessInputAsync("2"); await sessionGrain.ProcessInputAsync("231887654321"); await sessionGrain.ProcessInputAsync("50"); // Act - Cancel (option 2) var response = await sessionGrain.ProcessInputAsync("2"); // Assert Assert.Equal(UssdResponseType.Continue, response.ResponseType); Assert.Equal("MainMenu", response.CurrentMenu); } private async Task DeployApplication(ApplicationDefinition definition) { var appGrain = _fixture.Client.GetGrain<IApplicationGrain>( UssdGrainKey.ForApplication(_tenantId, definition.Name)); await appGrain.DeployAsync(definition); } private MockHttpClientFactory GetMockHttpClientFactory() { // This would need to be wired through the test cluster's service provider return new MockHttpClientFactory(); } }
Navigation Tests
Code// BankingApp/NavigationTests.cs using Tech231.Platform.Ussd.Integration.Tests.Builders; using Tech231.Platform.Ussd.Integration.Tests.Fixtures; using Tech231.Platform.Ussd.Orleans.Interfaces; using Tech231.Platform.Ussd.Domain.Utilities; namespace Tech231.Platform.Ussd.Integration.Tests.BankingApp; [Collection("Orleans")] public class NavigationTests { private readonly OrleansTestFixture _fixture; private readonly string _tenantId = OrleansTestFixture.TestTenantId; public NavigationTests(OrleansTestFixture fixture) { _fixture = fixture; } [Fact] public async Task BackNavigation_ReturnsToParentMenu() { // Arrange var sessionId = $"session-{Guid.NewGuid()}"; var appDefinition = TestApplications.BankingApp(); await DeployApplication(appDefinition); var sessionGrain = _fixture.Client.GetGrain<IUSSDSessionGrain>( UssdGrainKey.ForSession(_tenantId, sessionId)); await sessionGrain.StartSessionAsync(new SessionStartRequest( ApplicationName: appDefinition.Name, Msisdn: "231881234567", ShortCode: "*123#")); // Navigate to TransferMenu await sessionGrain.ProcessInputAsync("2"); // Act - Press Back (0) var response = await sessionGrain.ProcessInputAsync("0"); // Assert Assert.Equal(UssdResponseType.Continue, response.ResponseType); Assert.Equal("MainMenu", response.CurrentMenu); } [Fact] public async Task BackNavigation_AtStartMenu_StaysAtStart() { // Arrange var sessionId = $"session-{Guid.NewGuid()}"; var appDefinition = TestApplications.BankingApp(); await DeployApplication(appDefinition); var sessionGrain = _fixture.Client.GetGrain<IUSSDSessionGrain>( UssdGrainKey.ForSession(_tenantId, sessionId)); await sessionGrain.StartSessionAsync(new SessionStartRequest( ApplicationName: appDefinition.Name, Msisdn: "231881234567", ShortCode: "*123#")); // Act - Press Back at MainMenu var response = await sessionGrain.ProcessInputAsync("0"); // Assert Assert.Equal(UssdResponseType.Continue, response.ResponseType); Assert.Equal("MainMenu", response.CurrentMenu); } [Fact] public async Task HomeNavigation_ReturnsToStartFromDeepMenu() { // Arrange var sessionId = $"session-{Guid.NewGuid()}"; var appDefinition = TestApplications.BankingApp(); await DeployApplication(appDefinition); var sessionGrain = _fixture.Client.GetGrain<IUSSDSessionGrain>( UssdGrainKey.ForSession(_tenantId, sessionId)); await sessionGrain.StartSessionAsync(new SessionStartRequest( ApplicationName: appDefinition.Name, Msisdn: "231881234567", ShortCode: "*123#")); // Navigate deep: MainMenu -> TransferMenu -> TransferAmount await sessionGrain.ProcessInputAsync("2"); await sessionGrain.ProcessInputAsync("231887654321"); // Act - Press Home (00) var response = await sessionGrain.ProcessInputAsync("00"); // Assert Assert.Equal(UssdResponseType.Continue, response.ResponseType); Assert.Equal("MainMenu", response.CurrentMenu); // Verify navigation history is cleared var history = await sessionGrain.GetNavigationHistoryAsync(); Assert.Single(history); Assert.Equal("MainMenu", history[0]); } [Fact] public async Task NavigationHistory_TracksVisitedMenus() { // Arrange var sessionId = $"session-{Guid.NewGuid()}"; var appDefinition = TestApplications.BankingApp(); await DeployApplication(appDefinition); var sessionGrain = _fixture.Client.GetGrain<IUSSDSessionGrain>( UssdGrainKey.ForSession(_tenantId, sessionId)); await sessionGrain.StartSessionAsync(new SessionStartRequest( ApplicationName: appDefinition.Name, Msisdn: "231881234567", ShortCode: "*123#")); // Navigate: MainMenu -> TransferMenu -> TransferAmount await sessionGrain.ProcessInputAsync("2"); await sessionGrain.ProcessInputAsync("231887654321"); // Act var history = await sessionGrain.GetNavigationHistoryAsync(); // Assert Assert.Equal(3, history.Count); Assert.Equal("MainMenu", history[0]); Assert.Equal("TransferMenu", history[1]); Assert.Equal("TransferAmount", history[2]); } private async Task DeployApplication(ApplicationDefinition definition) { var appGrain = _fixture.Client.GetGrain<IApplicationGrain>( UssdGrainKey.ForApplication(_tenantId, definition.Name)); await appGrain.DeployAsync(definition); } }
Error Handling Tests
Code// BankingApp/ErrorHandlingTests.cs using Tech231.Platform.Ussd.Integration.Tests.Builders; using Tech231.Platform.Ussd.Integration.Tests.Fixtures; using Tech231.Platform.Ussd.Orleans.Interfaces; using Tech231.Platform.Ussd.Domain.Utilities; namespace Tech231.Platform.Ussd.Integration.Tests.BankingApp; [Collection("Orleans")] public class ErrorHandlingTests { private readonly OrleansTestFixture _fixture; private readonly string _tenantId = OrleansTestFixture.TestTenantId; public ErrorHandlingTests(OrleansTestFixture fixture) { _fixture = fixture; } [Fact] public async Task InvalidInput_ReturnsCurrentMenuWithError() { // Arrange var sessionId = $"session-{Guid.NewGuid()}"; var appDefinition = TestApplications.BankingApp(); await DeployApplication(appDefinition); var sessionGrain = _fixture.Client.GetGrain<IUSSDSessionGrain>( UssdGrainKey.ForSession(_tenantId, sessionId)); await sessionGrain.StartSessionAsync(new SessionStartRequest( ApplicationName: appDefinition.Name, Msisdn: "231881234567", ShortCode: "*123#")); // Act - Enter invalid option var response = await sessionGrain.ProcessInputAsync("9"); // Assert Assert.Equal(UssdResponseType.Continue, response.ResponseType); Assert.Equal("MainMenu", response.CurrentMenu); Assert.Contains("Invalid option", response.Text); } [Fact] public async Task SessionNotActive_ReturnsError() { // Arrange var sessionId = $"session-{Guid.NewGuid()}"; var sessionGrain = _fixture.Client.GetGrain<IUSSDSessionGrain>( UssdGrainKey.ForSession(_tenantId, sessionId)); // Act - Process input without starting session var response = await sessionGrain.ProcessInputAsync("1"); // Assert Assert.NotNull(response.Error); Assert.Equal("SESSION_NOT_ACTIVE", response.Error.Code); } [Fact] public async Task ApplicationNotDeployed_ReturnsError() { // Arrange var sessionId = $"session-{Guid.NewGuid()}"; var sessionGrain = _fixture.Client.GetGrain<IUSSDSessionGrain>( UssdGrainKey.ForSession(_tenantId, sessionId)); // Act - Start session with non-existent app var response = await sessionGrain.StartSessionAsync(new SessionStartRequest( ApplicationName: "non-existent-app", Msisdn: "231881234567", ShortCode: "*999#")); // Assert Assert.NotNull(response.Error); Assert.Contains("not found", response.Error.Message.ToLower()); } [Fact] public async Task TransferFailure_HandlesHttpError() { // Arrange var sessionId = $"session-{Guid.NewGuid()}"; var appDefinition = TestApplications.BankingApp(); await DeployApplication(appDefinition); var mockHttp = GetMockHttpClientFactory(); mockHttp.SetupError("/transfers", "Insufficient funds", System.Net.HttpStatusCode.BadRequest); var sessionGrain = _fixture.Client.GetGrain<IUSSDSessionGrain>( UssdGrainKey.ForSession(_tenantId, sessionId)); await sessionGrain.StartSessionAsync(new SessionStartRequest( ApplicationName: appDefinition.Name, Msisdn: "231881234567", ShortCode: "*123#")); // Navigate through transfer flow await sessionGrain.ProcessInputAsync("2"); await sessionGrain.ProcessInputAsync("231887654321"); await sessionGrain.ProcessInputAsync("50"); // Act - Confirm transfer (triggers HTTP call) var response = await sessionGrain.ProcessInputAsync("1"); // Assert Assert.Equal(UssdResponseType.End, response.ResponseType); Assert.Equal("TransferFailed", response.CurrentMenu); Assert.Contains("failed", response.Text.ToLower()); } private async Task DeployApplication(ApplicationDefinition definition) { var appGrain = _fixture.Client.GetGrain<IApplicationGrain>( UssdGrainKey.ForApplication(_tenantId, definition.Name)); await appGrain.DeployAsync(definition); } private MockHttpClientFactory GetMockHttpClientFactory() { return new MockHttpClientFactory(); } }
Test Helper Utilities
Code// Infrastructure/SessionTestHelper.cs using Tech231.Platform.Ussd.Orleans.Interfaces; namespace Tech231.Platform.Ussd.Integration.Tests.Infrastructure; /// <summary> /// Helper methods for USSD session testing. /// </summary> public static class SessionTestHelper { /// <summary> /// Executes a sequence of inputs and returns the final response. /// </summary> public static async Task<UssdResponse> ExecuteInputSequence( IUSSDSessionGrain sessionGrain, params string[] inputs) { UssdResponse? response = null; foreach (var input in inputs) { response = await sessionGrain.ProcessInputAsync(input); if (response.ResponseType == UssdResponseType.End || response.Error != null) { break; } } return response ?? throw new InvalidOperationException("No inputs provided"); } /// <summary> /// Asserts that the session is at the expected menu. /// </summary> public static void AssertAtMenu(UssdResponse response, string expectedMenu) { Assert.Equal(expectedMenu, response.CurrentMenu); } /// <summary> /// Asserts that the session has ended. /// </summary> public static void AssertSessionEnded(UssdResponse response) { Assert.Equal(UssdResponseType.End, response.ResponseType); } /// <summary> /// Asserts that the response contains expected text. /// </summary> public static void AssertTextContains(UssdResponse response, params string[] expectedTexts) { foreach (var text in expectedTexts) { Assert.Contains(text, response.Text); } } }
Running Integration Tests
Prerequisites
-
Ensure Orleans NuGet packages are installed:
Codedotnet add package Microsoft.Orleans.TestingHost -
Configure test project:
Code<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net10.0</TargetFramework> <IsPackable>false</IsPackable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Orleans.TestingHost" Version="9.0.*" /> <PackageReference Include="xunit" Version="2.9.*" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.8.*" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\src\Tech231.Platform.Ussd.Orleans\Tech231.Platform.Ussd.Orleans.csproj" /> <ProjectReference Include="..\..\src\Tech231.Platform.Ussd.Domain\Tech231.Platform.Ussd.Domain.csproj" /> </ItemGroup> </Project>
Execution
Code# Run all integration tests dotnet test src/Ussd/test/Tech231.Platform.Ussd.Integration.Tests # Run specific test class dotnet test --filter "FullyQualifiedName~BankingAppTests" # Run with verbose output dotnet test --logger "console;verbosity=detailed"
Test Coverage Matrix
| Test Category | Test Cases | Priority |
|---|---|---|
| Session Lifecycle | Start, terminate, timeout | P1 |
| Menu Navigation | Forward, back, home | P1 |
| Terminal Menus | Auto-end on terminal flag | P1 |
| Input Processing | Valid, invalid, wildcard | P1 |
| HTTP Actions | Success, failure, timeout | P2 |
| Variable Storage | Set, get, template render | P2 |
| Multi-tenant Isolation | Cross-tenant access denied | P2 |
| Error Handling | All error codes | P2 |
See Also
- USSD Request Pipeline - Full architecture documentation
- USSD Request Examples - Example payloads
- DSL Guide - DSL syntax reference
Last modified on