Skip to content

Commit

Permalink
Do not add all arguments of type T to the matching events, if one i…
Browse files Browse the repository at this point in the history
…s found (#1920)
  • Loading branch information
ITaluone committed May 18, 2022
1 parent c33006c commit 463edb9
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 44 deletions.
65 changes: 26 additions & 39 deletions Src/FluentAssertions/EventRaisingExtensions.cs
Expand Up @@ -53,49 +53,43 @@ public static IEventRecording WithSender(this IEventRecording eventRecording, ob
}

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

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

bool hasArgumentOfRightType = false;
var eventsMatchingPredicate = new List<OccurredEvent>();
var eventsWithMatchingPredicate = 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);
eventsWithMatchingPredicate.Add(@event);
}
}

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

if (!eventsMatchingPredicate.Any())
{
Execute.Assertion
.FailWith("Expected at least one event with arguments matching {0}, but found none.", predicate.Body);
}
Execute.Assertion
.ForCondition(foundMatchingEvent)
.FailWith("Expected at least one event which arguments are of type <{0}> and matches {1}, but found none.",
typeof(T),
predicate.Body);

return new FilteredEventRecording(eventRecording, eventsMatchingPredicate);
return new FilteredEventRecording(eventRecording, eventsWithMatchingPredicate);
}

/// <summary>
/// Asserts that at least one of the occurred events has arguments the match the predicates in the same order. Returns
/// only the events that matched those predicates.
/// Asserts that at least one occurence of the events had one or more arguments of the expected
/// type <typeparamref name="T"/> which matched the predicates in the same order.
/// Returns only the events that matched both type and optionally predicates.
/// </summary>
/// <remarks>
/// If a <c>null</c> is provided as predicate argument, the corresponding event parameter value is ignored.
Expand All @@ -104,49 +98,42 @@ 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>();
var eventsWithMatchingPredicate = 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;
}

if (isMatch)
{
eventsMatchingPredicate.Add(@event);
eventsWithMatchingPredicate.Add(@event);
}
}

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

if (!eventsMatchingPredicate.Any())
if (!foundMatchingEvent)
{
Execute
.Assertion
.FailWith("Expected at least one event with arguments matching {0}, but found none.",
string.Join(" | ", predicates.Where(p => p is not null).Select(p => p.Body.ToString())));
Execute.Assertion
.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);
return new FilteredEventRecording(eventRecording, eventsWithMatchingPredicate);
}
}
}
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 463edb9

Please sign in to comment.