Skip to main content
Version: 1.2

17. Testing Guide

17.1 Test projects (Shumoul.Saas.NotificationFramework repo)​

ProjectStackPurpose
Shumoul.Notification.Tests.UnitxUnit, NSubstitute, FluentAssertionsPure logic — retry decisions, failure classification, provider resolution, dispatch isolation, delivery engine, campaign workflow execution
Shumoul.Notification.Tests.IntegrationxUnit, NSubstitute, FluentAssertions, in-memory fakesPer-channel dispatch against fake repositories/persistence, verifying persistence side effects without a real database
Shumoul.Notification.Tests.HostxUnitDI wiring — BuildServiceProvider(validateScopes: true), adapter resolution, singleton lifetimes
Shumoul.Notification.Tests.PerformanceBenchmarkDotNetMicrobenchmarks on hot-path retry decision logic

Unit test classes (confirmed present in source at time of writing — 6 classes, up from an earlier 5-class baseline; exact per-class test-method counts should be confirmed by running the suite rather than assumed from an older report):

  • Domain\RetryDecisionPolicyTests.cs — ShouldRetry, GetNextDelaySeconds, IsDeadLetterEligible
  • Domain\NotificationFailureClassifierTests.cs — channel-aware failure-type classification
  • Providers\NotificationChannelProviderResolverTests.cs — registration, duplicate-channel detection
  • Dispatch\NotificationDispatchServiceTests.cs — provider isolation, per-channel exception handling
  • Delivery\DeliveryEngineServiceTests.cs — idempotency, retry scheduling, dead-letter creation
  • Campaign\CampaignWorkflowExecutorServiceTests.cs — campaign step execution (added after the original Phase 4 test baseline; confirms the campaign engine now has dedicated unit coverage)

Integration tests: DispatchIntegrationTests.cs, backed by Fakes/ in-memory implementations of IRetryRepository, IDeadLetterRepository, InMemoryAttemptRepository, InMemoryPersistenceContext, and a FakeRetryStrategy — exercises real dispatch-service code against fake persistence, verifying the right rows would be written without needing SQL Server.

17.2 Running the suite​

dotnet test E:\SaaS\Shumoul.Saas.NotificationFramework\Shumoul.Notification.Tests.Unit
dotnet test E:\SaaS\Shumoul.Saas.NotificationFramework\Shumoul.Notification.Tests.Integration
dotnet test E:\SaaS\Shumoul.Saas.NotificationFramework\Shumoul.Notification.Tests.Host
dotnet run --project E:\SaaS\Shumoul.Saas.NotificationFramework\Shumoul.Notification.Tests.Performance -c Release

17.3 Backend testing​

Run the four commands above before releasing a new Shumoul.Notification.* package version — see docs/ARCHITECTURE/TEST_STRATEGY.md and docs/ARCHITECTURE/REGRESSION_MATRIX.md for the platform's fuller regression matrix (65+ rows as of the Phase 4 baseline) covering both this framework and its ERP-side integration points.

17.4 Angular testing​

No Angular-specific test harness ships with this framework (it is backend-only). When testing an Angular integration against it:

  1. Mock the Result<T>/DtResult<T> envelope shapes documented in §9.0 in your HTTP interceptor tests, rather than hand-rolling ad-hoc response shapes.
  2. For SignalR, use a fake HubConnection (the @microsoft/signalr client is mockable) to simulate a "messages" event firing, and assert your store/badge update logic reacts correctly — see §10.5.

17.5 Flutter testing​

Similarly, mock the same Result<T> shapes for REST calls, and fake the HubConnection.on('messages', ...) callback registration to unit-test your notification bloc/provider without a live server.

17.6 Postman / manual API testing​

No Postman collection or .http file was found in the Shumoul.BackEnd\Shumoul repository at the time of this documentation pass. Use the request/response examples throughout Chapter 9 directly as the basis for manual curl/Postman requests, or generate a collection from the platform's Swagger/OpenAPI document (grouped by the GroupName values listed in §9.1).

17.7 SignalR testing​

To manually verify real-time delivery end to end:

  1. Connect a test client (browser console with the Angular sample from §10.5, or a small standalone SignalR client) using a valid JWT for a known user.
  2. Trigger a dispatch for an event whose configuration has EnableSignalR = true (§9.4) targeting that user's RecipientUserId.
  3. Confirm the "messages" event fires client-side with the expected UserNotificationDto payload (§10.4).
  4. Disconnect the client, trigger dispatch again, and confirm no client-side event fires (expected — SignalR has no queued redelivery) but an AppNotification row still appears via GetInbox on reconnect if the InApp channel was also enabled for that event.

17.8 Failure simulation​

To exercise the retry/dead-letter path described in Chapter 12 without waiting for a real provider outage:

  1. In a lower environment, temporarily misconfigure one channel's credentials (e.g. set an invalid MailSettings.Password, or an invalid WhatsAppCloudApi.AccessToken).
  2. Trigger a dispatch targeting that channel.
  3. Confirm a NotificationDeliveryAttempt row is created with Succeeded = false and a populated ErrorCode/ErrorMessage.
  4. Confirm a NotificationRetryQueue row appears with State = RetryScheduled, scheduled per that channel's NotificationDeliveryPolicy retry strategy (see §9.5).
  5. Wait for (or manually trigger via RetryNow) the next notification-retry-processor-pipeline run and confirm the attempt count increments.
  6. Repeat until MaxRetries is exceeded, and confirm the row transitions to DeadLetter state and a corresponding NotificationDeadLetter row is created (if DeadLetterEnabled is true for that policy).
  7. Revert the credential misconfiguration before continuing any other testing.

17.9 Retry simulation​

See §17.8 steps 3–5 — the same procedure exercises the retry engine specifically. To test the atomic-claim/orphan-repair logic directly, this requires two concurrent worker instances racing for the same NotificationRetryQueue row, which is impractical to simulate manually — rely on the unit tests in Delivery\DeliveryEngineServiceTests.cs for that guarantee instead.

17.10 Dead letter simulation​

See §17.8 step 6. To test Requeue specifically, call it against the resulting dead letter and confirm a fresh NotificationRetryQueue row is created from the preserved PayloadSnapshot.