Skip to content

Commit

Permalink
Do not add all arguments of type T to the matching events
Browse files Browse the repository at this point in the history
If at least one argument type is matching the expected type
all of the types are added to the filtered output.

This closes #1915
  • Loading branch information
ITaluone committed May 3, 2022
1 parent c33006c commit 49a8934
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 34 deletions.
42 changes: 13 additions & 29 deletions Src/FluentAssertions/EventRaisingExtensions.cs
Expand Up @@ -62,33 +62,25 @@ public static IEventRecording WithArgs<T>(this IEventRecording eventRecording, E

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

bool hasArgumentOfRightType = false;
var eventsMatchingPredicate = new List<OccurredEvent>();

foreach (OccurredEvent @event in eventRecording)
{
var typedParameters = @event.Parameters.OfType<T>().ToArray();
if (typedParameters.Any())
{
hasArgumentOfRightType = true;
}

if (typedParameters.Any(parameter => compiledPredicate(parameter)))
{
eventsMatchingPredicate.Add(@event);
}
}

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 with arguments matching either the type <{{0}}> or parameters {{1}}, but found none.",
typeof(T),
predicate.Body);

return new FilteredEventRecording(eventRecording, eventsMatchingPredicate);
}
Expand All @@ -110,18 +102,15 @@ public static IEventRecording WithArgs<T>(this IEventRecording eventRecording, p
foreach (OccurredEvent @event in eventRecording)
{
var typedParameters = @event.Parameters.OfType<T>().ToArray();
if (typedParameters.Any())
{
hasArgumentOfRightType = true;
}
hasArgumentOfRightType = typedParameters.Any();

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

bool isMatch = true;
bool isMatch = true && hasArgumentOfRightType;
for (int index = 0; index < predicates.Length && isMatch; index++)
{
isMatch = compiledPredicates[index]?.Invoke(typedParameters[index]) ?? true;
Expand All @@ -133,18 +122,13 @@ public static IEventRecording WithArgs<T>(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 with arguments matching either the type <{{0}}> or parameters {{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);
}
Expand Down
161 changes: 156 additions & 5 deletions Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs
Expand Up @@ -179,7 +179,7 @@ public void When_the_event_parameters_dont_match_it_should_throw()
act
.Should().Throw<XunitException>()
.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]
Expand All @@ -197,8 +197,8 @@ public void When_the_event_args_are_of_a_different_type_it_should_throw()

// Assert
act
.Should().Throw<ArgumentException>()
.WithMessage("No argument of event PropertyChanged is of type *CancelEventArgs>*");
.Should().Throw<XunitException>()
.WithMessage("Expected*event*argument*type*CancelEventArgs>*");
}

[Fact]
Expand Down Expand Up @@ -321,7 +321,7 @@ public void When_a_non_conventional_event_with_a_specific_argument_was_not_raise

// Assert
act.Should().Throw<XunitException>().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]
Expand All @@ -340,7 +340,7 @@ public void When_a_non_conventional_event_with_many_specific_arguments_was_not_r

// Assert
act.Should().Throw<XunitException>().WithMessage(
"Expected at least one event with arguments matching \"(args == \"" + wrongArgument +
"Expected at least one event with arguments matching*\"(args == \"" + wrongArgument +
"\")\", but found none.");
}

Expand Down Expand Up @@ -808,6 +808,157 @@ public void When_monitoring_interface_with_inherited_event_it_should_not_throw()
}
}

public class GetRecordingsForWithArgs
{
[Fact]
public void One_matching_argument_type_before_non_matching_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<B>();
filteredEvents.Should().HaveCount(1);
}

[Fact]
public void One_matching_argument_type_after_non_matching_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<B>();
filteredEvents.Should().HaveCount(1);
}

[Fact]
public void None_matching_argument_type_with_different_expected_argument_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>();

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected*event*argument*");
}

[Fact]
public void One_matching_argument_type_anywhere_between_non_matching_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<B>();
filteredEvents.Should().HaveCount(1);
}

[Fact]
public void One_matching_argument_type_anywhere_between_non_matching_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>(b => true);
filteredEvents.Should().HaveCount(1);
}

[Fact]
public void None_matching_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>(b => true);

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected*event*argument*type*B*none*");
}

[Fact]
public void None_matching_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>(b => true, b => false);

// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("Expected*event*parameters*type*B*found*");
}

[Fact]
public void One_matching_argument_type_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 B());

// Act
Action act = () => aMonitor.GetRecordingFor(nameof(A.Event)).WithArgs<B>(b => true, b => false);

// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("Expected*event*parameters*type*B*found*");
}
}

public class A
{
public event EventHandler<object> 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;
Expand Down
1 change: 1 addition & 0 deletions docs/_pages/releases.md
Expand Up @@ -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 arguments with wrong type, if at least one argument type is matching - [#1920](https://github.com/fluentassertions/fluentassertions/pull/1920)

## 6.6.0

Expand Down

0 comments on commit 49a8934

Please sign in to comment.