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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error messages for accidental Equals usage #2006

Merged
merged 23 commits into from Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from 14 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
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Numeric/NumericAssertions.cs
Expand Up @@ -474,7 +474,7 @@ public AndConstraint<TAssertions> NotBeOfType(Type unexpectedType, string becaus

/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
jnyrup marked this conversation as resolved.
Show resolved Hide resolved

private protected virtual bool IsNaN(T value) => false;

Expand Down
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Primitives/BooleanAssertions.cs
Expand Up @@ -120,5 +120,5 @@ public AndConstraint<TAssertions> NotBe(bool unexpected, string because = "", pa

/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
}
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Primitives/DateOnlyAssertions.cs
Expand Up @@ -512,7 +512,7 @@ public AndConstraint<TAssertions> BeOneOf(IEnumerable<DateOnly?> validValues, st

/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
}

#endif
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Primitives/DateTimeAssertions.cs
Expand Up @@ -925,5 +925,5 @@ public AndConstraint<TAssertions> BeIn(DateTimeKind expectedKind, string because

/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
}
Expand Up @@ -1094,5 +1094,5 @@ public AndConstraint<TAssertions> BeOneOf(IEnumerable<DateTimeOffset?> validValu

/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
}
Expand Up @@ -132,5 +132,5 @@ private static string PositionRelativeToTarget(DateTimeOffset actual, DateTimeOf

/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Before() or After() instead?");
nycdotnet marked this conversation as resolved.
Show resolved Hide resolved
dennisdoomen marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Primitives/DateTimeRangeAssertions.cs
Expand Up @@ -135,5 +135,5 @@ private static string PositionRelativeToTarget(DateTime actual, DateTime target)

/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Before() or After() instead?");
}
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Primitives/EnumAssertions.cs
Expand Up @@ -443,5 +443,5 @@ private static string GetName<T>(T @enum)

/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
}
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Primitives/GuidAssertions.cs
Expand Up @@ -171,5 +171,5 @@ public AndConstraint<TAssertions> NotBe(Guid unexpected, string because = "", pa

/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
dennisdoomen marked this conversation as resolved.
Show resolved Hide resolved
}
9 changes: 9 additions & 0 deletions Src/FluentAssertions/Primitives/ObjectAssertions.cs
Expand Up @@ -220,6 +220,15 @@ public AndConstraint<TAssertions> NotBe(TSubject unexpected, string because = ""
return new AndConstraint<TAssertions>((TAssertions)this);
}

#pragma warning disable CA1065 // "Exceptions should not be raised in this type of method..."; this is intentional to ensure that FluentAssertions' users get a suggestion of what to use instead.
nycdotnet marked this conversation as resolved.
Show resolved Hide resolved
/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() or BeSameAs() instead?");
nycdotnet marked this conversation as resolved.
Show resolved Hide resolved
#pragma warning restore CA1065

/// <inheritdoc/>
public override int GetHashCode() => base.GetHashCode();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configured linting rules were upset that I overrode Equals without also overriding GetHashCode().


/// <summary>
/// Returns the type of the subject the assertion applies on.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs
Expand Up @@ -422,5 +422,5 @@ public AndConstraint<TAssertions> NotBeAssignableTo(Type type, string because =

/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() or BeSameAs() instead?");
}
Expand Up @@ -301,5 +301,5 @@ public AndConstraint<TAssertions> BeGreaterThan(TimeSpan expected, string becaus

/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
}
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs
Expand Up @@ -566,7 +566,7 @@ public AndConstraint<TAssertions> BeOneOf(IEnumerable<TimeOnly?> validValues, st

/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
}

#endif
Expand Up @@ -231,5 +231,5 @@ public AndConstraint<ExecutionTimeAssertions> BeCloseTo(TimeSpan expectedDuratio

/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");
throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean BeLessThanOrEqualTo() or BeGreaterThanOrEqualTo() instead?");
}
Expand Up @@ -2100,6 +2100,8 @@ namespace FluentAssertions.Primitives
public FluentAssertions.AndConstraint<TAssertions> Be(TSubject expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeEquivalentTo<TExpectation>(TExpectation expectation, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeEquivalentTo<TExpectation>(TExpectation expectation, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
public override bool Equals(object obj) { }
jnyrup marked this conversation as resolved.
Show resolved Hide resolved
public override int GetHashCode() { }
public FluentAssertions.AndConstraint<TAssertions> NotBe(TSubject unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEquivalentTo<TExpectation>(TExpectation unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEquivalentTo<TExpectation>(TExpectation unexpected, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -2184,6 +2184,8 @@ namespace FluentAssertions.Primitives
public FluentAssertions.AndConstraint<TAssertions> Be(TSubject expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeEquivalentTo<TExpectation>(TExpectation expectation, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeEquivalentTo<TExpectation>(TExpectation expectation, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
public override bool Equals(object obj) { }
public override int GetHashCode() { }
public FluentAssertions.AndConstraint<TAssertions> NotBe(TSubject unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEquivalentTo<TExpectation>(TExpectation unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEquivalentTo<TExpectation>(TExpectation unexpected, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -2100,6 +2100,8 @@ namespace FluentAssertions.Primitives
public FluentAssertions.AndConstraint<TAssertions> Be(TSubject expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeEquivalentTo<TExpectation>(TExpectation expectation, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeEquivalentTo<TExpectation>(TExpectation expectation, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
public override bool Equals(object obj) { }
public override int GetHashCode() { }
public FluentAssertions.AndConstraint<TAssertions> NotBe(TSubject unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEquivalentTo<TExpectation>(TExpectation unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEquivalentTo<TExpectation>(TExpectation unexpected, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -2100,6 +2100,8 @@ namespace FluentAssertions.Primitives
public FluentAssertions.AndConstraint<TAssertions> Be(TSubject expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeEquivalentTo<TExpectation>(TExpectation expectation, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeEquivalentTo<TExpectation>(TExpectation expectation, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
public override bool Equals(object obj) { }
public override int GetHashCode() { }
public FluentAssertions.AndConstraint<TAssertions> NotBe(TSubject unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEquivalentTo<TExpectation>(TExpectation unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEquivalentTo<TExpectation>(TExpectation unexpected, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -2052,6 +2052,8 @@ namespace FluentAssertions.Primitives
public FluentAssertions.AndConstraint<TAssertions> Be(TSubject expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeEquivalentTo<TExpectation>(TExpectation expectation, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeEquivalentTo<TExpectation>(TExpectation expectation, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
public override bool Equals(object obj) { }
public override int GetHashCode() { }
public FluentAssertions.AndConstraint<TAssertions> NotBe(TSubject unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEquivalentTo<TExpectation>(TExpectation unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEquivalentTo<TExpectation>(TExpectation unexpected, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -2100,6 +2100,8 @@ namespace FluentAssertions.Primitives
public FluentAssertions.AndConstraint<TAssertions> Be(TSubject expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeEquivalentTo<TExpectation>(TExpectation expectation, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeEquivalentTo<TExpectation>(TExpectation expectation, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
public override bool Equals(object obj) { }
public override int GetHashCode() { }
public FluentAssertions.AndConstraint<TAssertions> NotBe(TSubject unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEquivalentTo<TExpectation>(TExpectation unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEquivalentTo<TExpectation>(TExpectation unexpected, System.Func<FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>, FluentAssertions.Equivalency.EquivalencyAssertionOptions<TExpectation>> config, string because = "", params object[] becauseArgs) { }
Expand Down
13 changes: 13 additions & 0 deletions Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.cs
Expand Up @@ -3963,4 +3963,17 @@ public void When_chaining_constraints_with_and_should_not_throw()
// Assert
action.Should().NotThrow();
}

[Fact]
public void Should_throw_a_helpful_error_when_accidentally_using_equals()
{
// Arrange
int value = 1;

// Act
Action action = () => value.Should().Equals(1);

// Assert
action.Should().Throw<NotSupportedException>().WithMessage("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
}
}
10 changes: 10 additions & 0 deletions Tests/FluentAssertions.Specs/Primitives/BooleanAssertionSpecs.cs
Expand Up @@ -143,4 +143,14 @@ public void Should_fail_with_descriptive_message_when_asserting_boolean_value_no
action.Should().Throw<XunitException>()
.WithMessage("*Expected*boolean*True*because we want to test the failure message, but found True.*");
}

[Fact]
public void Should_throw_a_helpful_error_when_accidentally_using_equals()
{
// Act
Action action = () => true.Should().Equals(true);

// Assert
action.Should().Throw<NotSupportedException>().WithMessage("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
}
}
16 changes: 16 additions & 0 deletions Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.cs
Expand Up @@ -771,6 +771,22 @@ public void Should_support_chaining_constraints_with_and()
.BeAfter(earlierDateOnly);
}
}

public class Miscellaneous
{
[Fact]
public void Should_throw_a_helpful_error_when_accidentally_using_equals()
{
// Arrange
DateOnly someDateOnly = new(2022, 9, 25);

// Act
Action action = () => someDateOnly.Should().Equals(someDateOnly);

// Assert
action.Should().Throw<NotSupportedException>().WithMessage("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
}
}
}

#endif
Expand Up @@ -1390,7 +1390,7 @@ public void When_asserting_subject_null_datetime_should_have_day_should_throw()
}
}

public class BotHaveDay
public class NotHaveDay
nycdotnet marked this conversation as resolved.
Show resolved Hide resolved
nycdotnet marked this conversation as resolved.
Show resolved Hide resolved
{
[Fact]
public void When_asserting_subject_datetime_should_not_have_day_with_the_same_value_it_should_throw()
Expand Down Expand Up @@ -2294,4 +2294,33 @@ public void When_a_value_is_one_of_the_specified_values_it_should_succeed_when_d
action.Should().NotThrow();
}
}

public class Miscellaneous
{
[Fact]
public void Should_throw_a_helpful_error_when_accidentally_using_equals()
{
// Arrange
DateTime someDateTime = new(2022, 9, 25, 13, 38, 42, DateTimeKind.Utc);

// Act
Action action = () => someDateTime.Should().Equals(someDateTime);

// Assert
action.Should().Throw<NotSupportedException>().WithMessage("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
}

[Fact]
public void Should_throw_a_helpful_error_when_accidentally_using_equals_with_a_range()
{
// Arrange
DateTime someDateTime = new(2022, 9, 25, 13, 38, 42, DateTimeKind.Utc);

// Act
Action action = () => someDateTime.Should().BeLessThan(0.Seconds()).Equals(someDateTime);

// Assert
action.Should().Throw<NotSupportedException>().WithMessage("Equals is not part of Fluent Assertions. Did you mean Before() or After() instead?");
}
}
}
Expand Up @@ -2437,6 +2437,19 @@ public void When_asserting_subject_be_less_than_10_seconds_before_target_but_sub
action.Should().Throw<XunitException>()
.WithMessage("Expected subject <00:00:45 +0h> to be less than 10s before <00:00:30 +0h>, but it is ahead by 15s.");
}

[Fact]
public void Should_throw_a_helpful_error_when_accidentally_using_equals_with_a_range()
{
// Arrange
DateTimeOffset someDateTimeOffset = new(2022, 9, 25, 13, 48, 42, 0, TimeSpan.Zero);

// Act
Action action = () => someDateTimeOffset.Should().BeLessThan(0.Seconds()).Equals(someDateTimeOffset);
jnyrup marked this conversation as resolved.
Show resolved Hide resolved

// Assert
action.Should().Throw<NotSupportedException>().WithMessage("Equals is not part of Fluent Assertions. Did you mean Before() or After() instead?");
}
}

public class ChainingConstraint
Expand Down Expand Up @@ -2530,4 +2543,20 @@ public void When_a_value_is_one_of_the_specified_values_it_should_succeed_when_d
action.Should().NotThrow();
}
}

public class Miscellaneous
{
[Fact]
public void Should_throw_a_helpful_error_when_accidentally_using_equals()
{
// Arrange
DateTimeOffset someDateTimeOffset = new(2022, 9, 25, 13, 48, 42, 0, TimeSpan.Zero);

// Act
Action action = () => someDateTimeOffset.Should().Equals(someDateTimeOffset);

// Assert
action.Should().Throw<NotSupportedException>().WithMessage("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
}
}
}
16 changes: 16 additions & 0 deletions Tests/FluentAssertions.Specs/Primitives/EnumAssertionSpecs.cs
Expand Up @@ -883,4 +883,20 @@ public void A_null_value_of_an_enum_is_not_defined_and_throws()
.WithMessage("Did not expect *to be defined in*, but found <null>.");
}
}

public class Miscellaneous
{
[Fact]
public void Should_throw_a_helpful_error_when_accidentally_using_equals()
{
// Arrange
MyEnum? subject = null;

// Act
Action action = () => subject.Should().Equals(subject);

// Assert
action.Should().Throw<NotSupportedException>().WithMessage("Equals is not part of Fluent Assertions. Did you mean Be() instead?");
}
}
}