diff --git a/src/Mocha/src/Mocha.Mediator/Mediator.cs b/src/Mocha/src/Mocha.Mediator/Mediator.cs index 83676d466ce..45898e8f6f5 100644 --- a/src/Mocha/src/Mocha.Mediator/Mediator.cs +++ b/src/Mocha/src/Mocha.Mediator/Mediator.cs @@ -137,6 +137,16 @@ private ValueTask PublishCoreAsync( { var pipelines = runtime.GetNotificationPipelines(messageType); + if (pipelines.IsEmpty) + { + if (notification is not INotification) + { + throw ThrowHelper.NotANotification(messageType); + } + + return default; + } + if (pipelines.Length == 1) { return PublishSingle(pipelines[0], notification, messageType, cancellationToken); diff --git a/src/Mocha/src/Mocha.Mediator/MediatorRuntime.cs b/src/Mocha/src/Mocha.Mediator/MediatorRuntime.cs index 46e89e8be1d..d2261ab0b76 100644 --- a/src/Mocha/src/Mocha.Mediator/MediatorRuntime.cs +++ b/src/Mocha/src/Mocha.Mediator/MediatorRuntime.cs @@ -98,7 +98,8 @@ public void ReturnContext(MediatorContext context) } /// - /// Gets the compiled notification pipeline delegates for the specified notification type. + /// Gets the compiled notification pipeline delegates for the specified notification type, + /// or an empty array if no handler is registered for it. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public ImmutableArray GetNotificationPipelines(Type notificationType) @@ -108,6 +109,6 @@ public ImmutableArray GetNotificationPipelines(Type notificati return pipelines; } - throw ThrowHelper.MissingNotificationPipeline(notificationType); + return []; } } diff --git a/src/Mocha/src/Mocha.Mediator/ThrowHelper.cs b/src/Mocha/src/Mocha.Mediator/ThrowHelper.cs index 0c6af56845c..63029d8f208 100644 --- a/src/Mocha/src/Mocha.Mediator/ThrowHelper.cs +++ b/src/Mocha/src/Mocha.Mediator/ThrowHelper.cs @@ -6,10 +6,11 @@ public static Exception MissingPipeline(Type messageType) => new InvalidOperationException( $"No pipeline registered for message type {messageType}"); - public static Exception MissingNotificationPipeline(Type notificationType) - => new InvalidOperationException( - $"No notification pipeline registered for message type {notificationType}. " - + "If this is a command or query, use SendAsync or QueryAsync instead."); + public static Exception NotANotification(Type type) + => new ArgumentException( + $"Type '{type}' does not implement {nameof(INotification)}. " + + "If this is a command or query, use SendAsync or QueryAsync instead.", + "notification"); public static Exception BeforeAndAfterConflict() => new ArgumentException( diff --git a/src/Mocha/test/Mocha.Mediator.Tests/MediatorDispatchTests.cs b/src/Mocha/test/Mocha.Mediator.Tests/MediatorDispatchTests.cs index 58ee179f081..1c964ed4904 100644 --- a/src/Mocha/test/Mocha.Mediator.Tests/MediatorDispatchTests.cs +++ b/src/Mocha/test/Mocha.Mediator.Tests/MediatorDispatchTests.cs @@ -162,9 +162,59 @@ await Assert.ThrowsAsync( () => mediator.SendAsync(new DispatchCommand("missing"), TestContext.Current.CancellationToken).AsTask()); await Assert.ThrowsAsync( () => mediator.QueryAsync(new DispatchQuery(1), TestContext.Current.CancellationToken).AsTask()); - await Assert.ThrowsAsync( - () => mediator.PublishAsync(new DispatchNotification("missing"), TestContext.Current.CancellationToken) - .AsTask()); + } + + [Fact] + public async Task PublishAsync_Should_Complete_When_NoNotificationHandlerRegistered() + { + // arrange + var sp = DispatchTestHelper.BuildProvider((_, _) => { }); + using var scope = sp.CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); + + // act + var task = mediator.PublishAsync(new DispatchNotification("unhandled"), TestContext.Current.CancellationToken); + + // assert + Assert.True(task.IsCompletedSuccessfully); + await task; + } + + [Fact] + public async Task PublishAsync_Should_Complete_When_UntypedNotificationHasNoHandler() + { + // arrange + var sp = DispatchTestHelper.BuildProvider((_, _) => { }); + using var scope = sp.CreateScope(); + var publisher = scope.ServiceProvider.GetRequiredService(); + + // act + var task = publisher.PublishAsync( + (object)new DispatchNotification("unhandled"), + TestContext.Current.CancellationToken); + + // assert + Assert.True(task.IsCompletedSuccessfully); + await task; + } + + [Fact] + public async Task PublishAsync_Should_ThrowArgumentException_When_ObjectIsNotANotification() + { + // arrange + var sp = DispatchTestHelper.BuildProvider((_, _) => { }); + using var scope = sp.CreateScope(); + var publisher = scope.ServiceProvider.GetRequiredService(); + + // act + var exception = await Assert.ThrowsAsync( + () => publisher.PublishAsync(new DispatchVoidCommand("x"), TestContext.Current.CancellationToken).AsTask()); + + // assert + Assert.Equal( + "Type 'Mocha.Mediator.Tests.DispatchVoidCommand' does not implement INotification. " + + "If this is a command or query, use SendAsync or QueryAsync instead. (Parameter 'notification')", + exception.Message); } [Fact]