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 6, 2022
1 parent c33006c commit dc62e9b
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 35 deletions.
43 changes: 13 additions & 30 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 which arguments are of type <{{0}}> and which matches {{1}}, but found none.",
typeof(T),
predicate.Body);

return new FilteredEventRecording(eventRecording, eventsMatchingPredicate);
}
Expand All @@ -104,24 +96,20 @@ public static IEventRecording WithArgs<T>(this IEventRecording eventRecording, p
{
Func<T, bool>[] compiledPredicates = predicates.Select(p => p?.Compile()).ToArray();

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;
}
bool 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 = hasArgumentOfRightType;
for (int index = 0; index < predicates.Length && isMatch; index++)
{
isMatch = compiledPredicates[index]?.Invoke(typedParameters[index]) ?? true;
Expand All @@ -133,18 +121,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 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);
}
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 which arguments are of type*PropertyChangedEventArgs*matches*(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 which arguments*type*Int32*matches*(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 which arguments*matches*\"(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 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<B>();
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<B>();
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<B>();

// Assert
act.Should().Throw<XunitException>()
.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<B>();
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>(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>(b => true);

// Assert
act.Should().Throw<XunitException>()
.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>(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_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>(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 too many events when at least one argument matched the expected type - [#1920](https://github.com/fluentassertions/fluentassertions/pull/1920)

## 6.6.0

Expand Down

0 comments on commit dc62e9b

Please sign in to comment.