Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not add all arguments of type T to the matching events, if one is found #1920

Merged
merged 2 commits into from May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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())
ITaluone marked this conversation as resolved.
Show resolved Hide resolved
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