From a04062174f0593b085be27f842e085d905001bc5 Mon Sep 17 00:00:00 2001 From: Lukas Gasselsberger | alu-one Date: Tue, 3 May 2022 07:20:25 +0200 Subject: [PATCH] Do not add all arguments of type `T` to the matching events If at least one argument type is matching the expected type all of the types are added to the filtered output. This closes #1915 --- .../EventRaisingExtensions.cs | 43 ++--- .../Events/EventAssertionSpecs.cs | 161 +++++++++++++++++- docs/_pages/releases.md | 1 + 3 files changed, 170 insertions(+), 35 deletions(-) diff --git a/Src/FluentAssertions/EventRaisingExtensions.cs b/Src/FluentAssertions/EventRaisingExtensions.cs index f73e98a6ec..20d6bd2ee9 100644 --- a/Src/FluentAssertions/EventRaisingExtensions.cs +++ b/Src/FluentAssertions/EventRaisingExtensions.cs @@ -62,16 +62,11 @@ public static IEventRecording WithArgs(this IEventRecording eventRecording, E Func compiledPredicate = predicate.Compile(); - bool hasArgumentOfRightType = false; var eventsMatchingPredicate = new List(); foreach (OccurredEvent @event in eventRecording) { var typedParameters = @event.Parameters.OfType().ToArray(); - if (typedParameters.Any()) - { - hasArgumentOfRightType = true; - } if (typedParameters.Any(parameter => compiledPredicate(parameter))) { @@ -79,16 +74,13 @@ public static IEventRecording WithArgs(this IEventRecording eventRecording, E } } - if (!hasArgumentOfRightType) - { - throw new ArgumentException("No argument of event " + eventRecording.EventName + " is of type <" + typeof(T) + ">."); - } + bool hasFoundArgumentsBefore = eventsMatchingPredicate.Any(); - if (!eventsMatchingPredicate.Any()) - { - Execute.Assertion - .FailWith("Expected at least one event with arguments matching {0}, but found none.", predicate.Body); - } + Execute.Assertion + .ForCondition(hasFoundArgumentsBefore) + .FailWith($"Expected at least one event which arguments are of type <{{0}}> and which matches {{1}}, but found none.", + typeof(T), + predicate.Body); return new FilteredEventRecording(eventRecording, eventsMatchingPredicate); } @@ -104,16 +96,12 @@ public static IEventRecording WithArgs(this IEventRecording eventRecording, p { Func[] compiledPredicates = predicates.Select(p => p?.Compile()).ToArray(); - bool hasArgumentOfRightType = false; var eventsMatchingPredicate = new List(); foreach (OccurredEvent @event in eventRecording) { var typedParameters = @event.Parameters.OfType().ToArray(); - if (typedParameters.Any()) - { - hasArgumentOfRightType = true; - } + bool hasArgumentOfRightType = typedParameters.Any(); if (predicates.Length > typedParameters.Length) { @@ -121,7 +109,7 @@ public static IEventRecording WithArgs(this IEventRecording eventRecording, p $"Expected the event to have at least {predicates.Length} parameters of type {typeof(T)}, but only found {typedParameters.Length}."); } - bool isMatch = true; + bool isMatch = hasArgumentOfRightType; for (int index = 0; index < predicates.Length && isMatch; index++) { isMatch = compiledPredicates[index]?.Invoke(typedParameters[index]) ?? true; @@ -133,18 +121,13 @@ public static IEventRecording WithArgs(this IEventRecording eventRecording, p } } - if (!hasArgumentOfRightType) - { - throw new ArgumentException("No argument of event " + eventRecording.EventName + " is of type <" + typeof(T) + ">."); - } + bool hasFoundArgumentsBefore = eventsMatchingPredicate.Any(); - if (!eventsMatchingPredicate.Any()) - { - Execute - .Assertion - .FailWith("Expected at least one event with arguments matching {0}, but found none.", + Execute.Assertion + .ForCondition(hasFoundArgumentsBefore) + .FailWith($"Expected at least one event which arguments are of type <{{0}}> and matches ({{1}}), but found none.", + typeof(T), string.Join(" | ", predicates.Where(p => p is not null).Select(p => p.Body.ToString()))); - } return new FilteredEventRecording(eventRecording, eventsMatchingPredicate); } diff --git a/Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs index 871bb98455..0a029d039a 100644 --- a/Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs @@ -179,7 +179,7 @@ public void When_the_event_parameters_dont_match_it_should_throw() act .Should().Throw() .WithMessage( - "Expected at least one event with arguments matching (args.PropertyName == \"SomeProperty\"), but found none."); + "Expected at least one event with arguments matching*type*PropertyChangedEventArgs*parameter*(args.PropertyName == \"SomeProperty\"), but found none."); } [Fact] @@ -197,8 +197,8 @@ public void When_the_event_args_are_of_a_different_type_it_should_throw() // Assert act - .Should().Throw() - .WithMessage("No argument of event PropertyChanged is of type *CancelEventArgs>*"); + .Should().Throw() + .WithMessage("Expected*event*argument*type*CancelEventArgs>*"); } [Fact] @@ -321,7 +321,7 @@ public void When_a_non_conventional_event_with_a_specific_argument_was_not_raise // Assert act.Should().Throw().WithMessage( - "Expected at least one event with arguments matching (args == " + wrongArgument + "), but found none."); + "Expected at least one event with arguments matching*type*Int32*parameters*(args == " + wrongArgument + "), but found none."); } [Fact] @@ -340,7 +340,7 @@ public void When_a_non_conventional_event_with_many_specific_arguments_was_not_r // Assert act.Should().Throw().WithMessage( - "Expected at least one event with arguments matching \"(args == \"" + wrongArgument + + "Expected at least one event with arguments matching*\"(args == \"" + wrongArgument + "\")\", but found none."); } @@ -808,6 +808,157 @@ public void When_monitoring_interface_with_inherited_event_it_should_not_throw() } } + public class WithArgs + { + [Fact] + public void One_matching_argument_type_before_mismatching_types_passes() + { + // Arrange + A a = new A(); + using var aMonitor = a.Monitor(); + + a.OnEvent(new B()); + a.OnEvent(new C()); + + // Act / Assert + IEventRecording filteredEvents = aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs(); + filteredEvents.Should().HaveCount(1); + } + + [Fact] + public void One_matching_argument_type_after_mismatching_types_passes() + { + // Arrange + A a = new A(); + using var aMonitor = a.Monitor(); + + a.OnEvent(new C()); + a.OnEvent(new B()); + + // Act / Assert + IEventRecording filteredEvents = aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs(); + filteredEvents.Should().HaveCount(1); + } + + [Fact] + public void Throws_when_none_of_the_arguments_are_of_the_expected_type() + { + // Arrange + A a = new A(); + using var aMonitor = a.Monitor(); + + a.OnEvent(new C()); + a.OnEvent(new C()); + + // Act + Action act = () => aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs(); + + // Assert + act.Should().Throw() + .WithMessage("Expected*event*argument*"); + } + + [Fact] + public void One_matching_argument_type_anywhere_between_mismatching_types_passes() + { + // Arrange + A a = new A(); + using var aMonitor = a.Monitor(); + + a.OnEvent(new C()); + a.OnEvent(new B()); + a.OnEvent(new C()); + + // Act / Assert + IEventRecording filteredEvents = aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs(); + filteredEvents.Should().HaveCount(1); + } + + [Fact] + public void One_matching_argument_type_anywhere_between_mismatching_types_with_parameters_passes() + { + // Arrange + A a = new A(); + using var aMonitor = a.Monitor(); + + a.OnEvent(new C()); + a.OnEvent(new B()); + a.OnEvent(new C()); + + // Act / Assert + IEventRecording filteredEvents = aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs(b => true); + filteredEvents.Should().HaveCount(1); + } + + [Fact] + public void Mismatching_argument_types_with_one_parameter_matching_a_different_type_fails() + { + // Arrange + A a = new A(); + using var aMonitor = a.Monitor(); + + a.OnEvent(new C()); + a.OnEvent(new C()); + + // Act + Action act = () => aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs(b => true); + + // Assert + act.Should().Throw() + .WithMessage("Expected*event*argument*type*B*none*"); + } + + [Fact] + public void Mismatching_argument_types_with_two_or_more_parameters_matching_a_different_type_fails() + { + // Arrange + A a = new A(); + using var aMonitor = a.Monitor(); + + a.OnEvent(new C()); + a.OnEvent(new C()); + + // Act + Action act = () => aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs(b => true, b => false); + + // Assert + act.Should().Throw() + .WithMessage("Expected*event*parameters*type*B*found*"); + } + + [Fact] + public void One_matching_argument_type_with_two_or_more_parameters_matching_a_mismatching_type_fails() + { + // Arrange + A a = new A(); + using var aMonitor = a.Monitor(); + + a.OnEvent(new C()); + a.OnEvent(new B()); + + // Act + Action act = () => aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs(b => true, b => false); + + // Assert + act.Should().Throw() + .WithMessage("Expected*event*parameters*type*B*found*"); + } + } + + public class A + { + public event EventHandler Event; + + public void OnEvent(object o) + { + Event.Invoke(nameof(A), o); + } + } + + public class B { } + + public class C { } + public class ClassThatRaisesEventsItself : IInheritsEventRaisingInterface { public event PropertyChangedEventHandler PropertyChanged; diff --git a/docs/_pages/releases.md b/docs/_pages/releases.md index ada326d6ad..af80aee431 100644 --- a/docs/_pages/releases.md +++ b/docs/_pages/releases.md @@ -18,6 +18,7 @@ sidebar: ### Fixes * Fix the failure message for regex matches (occurrence overload) to include the missing subject - [#1913](https://github.com/fluentassertions/fluentassertions/pull/1913) +* Fixed `WithArgs` matching too many events when at least one argument matched the expected type - [#1920](https://github.com/fluentassertions/fluentassertions/pull/1920) ## 6.6.0