Skip to content

Commit 463edb9

Browse files
authored
Do not add all arguments of type T to the matching events, if one is found (#1920)
1 parent c33006c commit 463edb9

File tree

3 files changed

+183
-44
lines changed

3 files changed

+183
-44
lines changed

Src/FluentAssertions/EventRaisingExtensions.cs

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -53,49 +53,43 @@ public static IEventRecording WithSender(this IEventRecording eventRecording, ob
5353
}
5454

5555
/// <summary>
56-
/// Asserts that at least one occurrence of the events had at least one of the arguments matching a predicate. Returns
57-
/// only the events that matched that predicate.
56+
/// Asserts that at least one occurence of the events had one or more arguments of the expected
57+
/// type <typeparamref name="T"/> which matched the given predicate.
58+
/// Returns only the events that matched both type and optionally a predicate.
5859
/// </summary>
5960
public static IEventRecording WithArgs<T>(this IEventRecording eventRecording, Expression<Func<T, bool>> predicate)
6061
{
6162
Guard.ThrowIfArgumentIsNull(predicate, nameof(predicate));
6263

6364
Func<T, bool> compiledPredicate = predicate.Compile();
6465

65-
bool hasArgumentOfRightType = false;
66-
var eventsMatchingPredicate = new List<OccurredEvent>();
66+
var eventsWithMatchingPredicate = new List<OccurredEvent>();
6767

6868
foreach (OccurredEvent @event in eventRecording)
6969
{
7070
var typedParameters = @event.Parameters.OfType<T>().ToArray();
71-
if (typedParameters.Any())
72-
{
73-
hasArgumentOfRightType = true;
74-
}
7571

7672
if (typedParameters.Any(parameter => compiledPredicate(parameter)))
7773
{
78-
eventsMatchingPredicate.Add(@event);
74+
eventsWithMatchingPredicate.Add(@event);
7975
}
8076
}
8177

82-
if (!hasArgumentOfRightType)
83-
{
84-
throw new ArgumentException("No argument of event " + eventRecording.EventName + " is of type <" + typeof(T) + ">.");
85-
}
78+
bool foundMatchingEvent = eventsWithMatchingPredicate.Any();
8679

87-
if (!eventsMatchingPredicate.Any())
88-
{
89-
Execute.Assertion
90-
.FailWith("Expected at least one event with arguments matching {0}, but found none.", predicate.Body);
91-
}
80+
Execute.Assertion
81+
.ForCondition(foundMatchingEvent)
82+
.FailWith("Expected at least one event which arguments are of type <{0}> and matches {1}, but found none.",
83+
typeof(T),
84+
predicate.Body);
9285

93-
return new FilteredEventRecording(eventRecording, eventsMatchingPredicate);
86+
return new FilteredEventRecording(eventRecording, eventsWithMatchingPredicate);
9487
}
9588

9689
/// <summary>
97-
/// Asserts that at least one of the occurred events has arguments the match the predicates in the same order. Returns
98-
/// only the events that matched those predicates.
90+
/// Asserts that at least one occurence of the events had one or more arguments of the expected
91+
/// type <typeparamref name="T"/> which matched the predicates in the same order.
92+
/// Returns only the events that matched both type and optionally predicates.
9993
/// </summary>
10094
/// <remarks>
10195
/// If a <c>null</c> is provided as predicate argument, the corresponding event parameter value is ignored.
@@ -104,49 +98,42 @@ public static IEventRecording WithArgs<T>(this IEventRecording eventRecording, p
10498
{
10599
Func<T, bool>[] compiledPredicates = predicates.Select(p => p?.Compile()).ToArray();
106100

107-
bool hasArgumentOfRightType = false;
108-
var eventsMatchingPredicate = new List<OccurredEvent>();
101+
var eventsWithMatchingPredicate = new List<OccurredEvent>();
109102

110103
foreach (OccurredEvent @event in eventRecording)
111104
{
112105
var typedParameters = @event.Parameters.OfType<T>().ToArray();
113-
if (typedParameters.Any())
114-
{
115-
hasArgumentOfRightType = true;
116-
}
106+
bool hasArgumentOfRightType = typedParameters.Any();
117107

118108
if (predicates.Length > typedParameters.Length)
119109
{
120110
throw new ArgumentException(
121111
$"Expected the event to have at least {predicates.Length} parameters of type {typeof(T)}, but only found {typedParameters.Length}.");
122112
}
123113

124-
bool isMatch = true;
114+
bool isMatch = hasArgumentOfRightType;
125115
for (int index = 0; index < predicates.Length && isMatch; index++)
126116
{
127117
isMatch = compiledPredicates[index]?.Invoke(typedParameters[index]) ?? true;
128118
}
129119

130120
if (isMatch)
131121
{
132-
eventsMatchingPredicate.Add(@event);
122+
eventsWithMatchingPredicate.Add(@event);
133123
}
134124
}
135125

136-
if (!hasArgumentOfRightType)
137-
{
138-
throw new ArgumentException("No argument of event " + eventRecording.EventName + " is of type <" + typeof(T) + ">.");
139-
}
126+
bool foundMatchingEvent = eventsWithMatchingPredicate.Any();
140127

141-
if (!eventsMatchingPredicate.Any())
128+
if (!foundMatchingEvent)
142129
{
143-
Execute
144-
.Assertion
145-
.FailWith("Expected at least one event with arguments matching {0}, but found none.",
146-
string.Join(" | ", predicates.Where(p => p is not null).Select(p => p.Body.ToString())));
130+
Execute.Assertion
131+
.FailWith("Expected at least one event which arguments are of type <{0}> and matches {1}, but found none.",
132+
typeof(T),
133+
string.Join(" | ", predicates.Where(p => p is not null).Select(p => p.Body.ToString())));
147134
}
148135

149-
return new FilteredEventRecording(eventRecording, eventsMatchingPredicate);
136+
return new FilteredEventRecording(eventRecording, eventsWithMatchingPredicate);
150137
}
151138
}
152139
}

Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs

Lines changed: 156 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ public void When_the_event_parameters_dont_match_it_should_throw()
179179
act
180180
.Should().Throw<XunitException>()
181181
.WithMessage(
182-
"Expected at least one event with arguments matching (args.PropertyName == \"SomeProperty\"), but found none.");
182+
"Expected at least one event which arguments are of type*PropertyChangedEventArgs*matches*(args.PropertyName == \"SomeProperty\"), but found none.");
183183
}
184184

185185
[Fact]
@@ -197,8 +197,8 @@ public void When_the_event_args_are_of_a_different_type_it_should_throw()
197197

198198
// Assert
199199
act
200-
.Should().Throw<ArgumentException>()
201-
.WithMessage("No argument of event PropertyChanged is of type *CancelEventArgs>*");
200+
.Should().Throw<XunitException>()
201+
.WithMessage("Expected*event*argument*type*CancelEventArgs>*");
202202
}
203203

204204
[Fact]
@@ -321,7 +321,7 @@ public void When_a_non_conventional_event_with_a_specific_argument_was_not_raise
321321

322322
// Assert
323323
act.Should().Throw<XunitException>().WithMessage(
324-
"Expected at least one event with arguments matching (args == " + wrongArgument + "), but found none.");
324+
"Expected at least one event which arguments*type*Int32*matches*(args == " + wrongArgument + "), but found none.");
325325
}
326326

327327
[Fact]
@@ -340,7 +340,7 @@ public void When_a_non_conventional_event_with_many_specific_arguments_was_not_r
340340

341341
// Assert
342342
act.Should().Throw<XunitException>().WithMessage(
343-
"Expected at least one event with arguments matching \"(args == \"" + wrongArgument +
343+
"Expected at least one event which arguments*matches*\"(args == \"" + wrongArgument +
344344
"\")\", but found none.");
345345
}
346346

@@ -808,6 +808,157 @@ public void When_monitoring_interface_with_inherited_event_it_should_not_throw()
808808
}
809809
}
810810

811+
public class WithArgs
812+
{
813+
[Fact]
814+
public void One_matching_argument_type_before_mismatching_types_passes()
815+
{
816+
// Arrange
817+
A a = new A();
818+
using var aMonitor = a.Monitor();
819+
820+
a.OnEvent(new B());
821+
a.OnEvent(new C());
822+
823+
// Act / Assert
824+
IEventRecording filteredEvents = aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs<B>();
825+
filteredEvents.Should().HaveCount(1);
826+
}
827+
828+
[Fact]
829+
public void One_matching_argument_type_after_mismatching_types_passes()
830+
{
831+
// Arrange
832+
A a = new A();
833+
using var aMonitor = a.Monitor();
834+
835+
a.OnEvent(new C());
836+
a.OnEvent(new B());
837+
838+
// Act / Assert
839+
IEventRecording filteredEvents = aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs<B>();
840+
filteredEvents.Should().HaveCount(1);
841+
}
842+
843+
[Fact]
844+
public void Throws_when_none_of_the_arguments_are_of_the_expected_type()
845+
{
846+
// Arrange
847+
A a = new A();
848+
using var aMonitor = a.Monitor();
849+
850+
a.OnEvent(new C());
851+
a.OnEvent(new C());
852+
853+
// Act
854+
Action act = () => aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs<B>();
855+
856+
// Assert
857+
act.Should().Throw<XunitException>()
858+
.WithMessage("Expected*event*argument*");
859+
}
860+
861+
[Fact]
862+
public void One_matching_argument_type_anywhere_between_mismatching_types_passes()
863+
{
864+
// Arrange
865+
A a = new A();
866+
using var aMonitor = a.Monitor();
867+
868+
a.OnEvent(new C());
869+
a.OnEvent(new B());
870+
a.OnEvent(new C());
871+
872+
// Act / Assert
873+
IEventRecording filteredEvents = aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs<B>();
874+
filteredEvents.Should().HaveCount(1);
875+
}
876+
877+
[Fact]
878+
public void One_matching_argument_type_anywhere_between_mismatching_types_with_parameters_passes()
879+
{
880+
// Arrange
881+
A a = new A();
882+
using var aMonitor = a.Monitor();
883+
884+
a.OnEvent(new C());
885+
a.OnEvent(new B());
886+
a.OnEvent(new C());
887+
888+
// Act / Assert
889+
IEventRecording filteredEvents = aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs<B>(b => true);
890+
filteredEvents.Should().HaveCount(1);
891+
}
892+
893+
[Fact]
894+
public void Mismatching_argument_types_with_one_parameter_matching_a_different_type_fails()
895+
{
896+
// Arrange
897+
A a = new A();
898+
using var aMonitor = a.Monitor();
899+
900+
a.OnEvent(new C());
901+
a.OnEvent(new C());
902+
903+
// Act
904+
Action act = () => aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs<B>(b => true);
905+
906+
// Assert
907+
act.Should().Throw<XunitException>()
908+
.WithMessage("Expected*event*argument*type*B*none*");
909+
}
910+
911+
[Fact]
912+
public void Mismatching_argument_types_with_two_or_more_parameters_matching_a_different_type_fails()
913+
{
914+
// Arrange
915+
A a = new A();
916+
using var aMonitor = a.Monitor();
917+
918+
a.OnEvent(new C());
919+
a.OnEvent(new C());
920+
921+
// Act
922+
Action act = () => aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs<B>(b => true, b => false);
923+
924+
// Assert
925+
act.Should().Throw<ArgumentException>()
926+
.WithMessage("Expected*event*parameters*type*B*found*");
927+
}
928+
929+
[Fact]
930+
public void One_matching_argument_type_with_two_or_more_parameters_matching_a_mismatching_type_fails()
931+
{
932+
// Arrange
933+
A a = new A();
934+
using var aMonitor = a.Monitor();
935+
936+
a.OnEvent(new C());
937+
a.OnEvent(new B());
938+
939+
// Act
940+
Action act = () => aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs<B>(b => true, b => false);
941+
942+
// Assert
943+
act.Should().Throw<ArgumentException>()
944+
.WithMessage("Expected*event*parameters*type*B*found*");
945+
}
946+
}
947+
948+
public class A
949+
{
950+
public event EventHandler<object> Event;
951+
952+
public void OnEvent(object o)
953+
{
954+
Event.Invoke(nameof(A), o);
955+
}
956+
}
957+
958+
public class B { }
959+
960+
public class C { }
961+
811962
public class ClassThatRaisesEventsItself : IInheritsEventRaisingInterface
812963
{
813964
public event PropertyChangedEventHandler PropertyChanged;

docs/_pages/releases.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ sidebar:
1818

1919
### Fixes
2020
* Fix the failure message for regex matches (occurrence overload) to include the missing subject - [#1913](https://github.com/fluentassertions/fluentassertions/pull/1913)
21+
* Fixed `WithArgs` matching too many events when at least one argument matched the expected type - [#1920](https://github.com/fluentassertions/fluentassertions/pull/1920)
2122

2223
## 6.6.0
2324

0 commit comments

Comments
 (0)