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.csShouldRetry, 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.