Skip to content

Commit

Permalink
Add NotBeCloseTo() to TimeOnlyAssertions
Browse files Browse the repository at this point in the history
  • Loading branch information
IT-VBFK committed Nov 8, 2022
1 parent 713b5f9 commit 88e435b
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 31 deletions.
55 changes: 48 additions & 7 deletions Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs
Expand Up @@ -131,10 +131,6 @@ public AndConstraint<TAssertions> Be(TimeOnly? expected, string because = "", pa
/// Asserts that the current <see cref="TimeOnly"/> is within the specified time
/// from the specified <paramref name="nearbyTime"/> value.
/// </summary>
/// <remarks>
/// Use this assertion when, for example the database truncates datetimes to nearest 20ms. If you want to assert to the exact datetime,
/// use <see cref="Be(TimeOnly, string, object[])"/>.
/// </remarks>
/// <param name="nearbyTime">
/// The expected time to compare the actual value with.
/// </param>
Expand Down Expand Up @@ -168,12 +164,57 @@ public AndConstraint<TAssertions> Be(TimeOnly? expected, string because = "", pa

Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Expected {context:the time} to be within {0} from {1}{reason}", precision, nearbyTime)
.WithExpectation("Expected {context:the time} to be within {0} from {1}{reason}, ", precision, nearbyTime)
.ForCondition(Subject is not null)
.FailWith(", but found <null>.")
.FailWith("but found <null>.")
.Then
.ForCondition((Subject >= minimumValue) && (Subject <= maximumValue))
.FailWith(", but {0} was off by {1}.", Subject, difference)
.FailWith("but {0} was off by {1}.", Subject, difference)
.Then
.ClearExpectation();

return new AndConstraint<TAssertions>((TAssertions)this);
}

/// <summary>
/// Asserts that the current <see cref="TimeOnly"/> is not within the specified time
/// from the specified <paramref name="distantTime"/> value.
/// </summary>
/// <param name="distantTime">
/// The time to compare the actual value with.
/// </param>
/// <param name="precision">
/// The maximum amount of time which the two values must differ.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public AndConstraint<TAssertions> NotBeCloseTo(TimeOnly distantTime, TimeSpan precision, string because = "",
params object[] becauseArgs)
{
if (precision < TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(precision), $"The value of {nameof(precision)} must be non-negative.");
}

long distanceToMinInTicks = (distantTime - TimeOnly.MinValue).Ticks;
TimeOnly minimumValue = distantTime.Add(TimeSpan.FromTicks(-Math.Min(precision.Ticks, distanceToMinInTicks)));

long distanceToMaxInTicks = (TimeOnly.MaxValue - distantTime).Ticks;
TimeOnly maximumValue = distantTime.Add(TimeSpan.FromTicks(Math.Min(precision.Ticks, distanceToMaxInTicks)));

Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Did not expect {context:the time} to be within {0} from {1}{reason}, ", precision, distantTime)
.ForCondition(Subject is not null)
.FailWith("but found <null>.")
.Then
.ForCondition((Subject < minimumValue) || (Subject > maximumValue))
.FailWith("but it was {0}.", Subject)
.Then
.ClearExpectation();

Expand Down
Expand Up @@ -2324,6 +2324,7 @@ namespace FluentAssertions.Primitives
public FluentAssertions.AndConstraint<TAssertions> NotBe(System.TimeOnly? unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeAfter(System.TimeOnly unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeBefore(System.TimeOnly unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeCloseTo(System.TimeOnly distantTime, System.TimeSpan precision, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeOnOrAfter(System.TimeOnly unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeOnOrBefore(System.TimeOnly unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotHaveHours(int unexpected, string because = "", params object[] becauseArgs) { }
Expand Down
234 changes: 210 additions & 24 deletions Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.cs
Expand Up @@ -195,11 +195,8 @@ public void When_a_time_is_close_to_a_later_time_by_one_tick_it_should_succeed()
var dateTime = TimeOnly.FromDateTime(DateTime.UtcNow);
var actual = new TimeOnly(dateTime.Ticks - 1);

// Act
Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1));

// Assert
act.Should().NotThrow();
// Act / Assert
actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1));
}

[Fact]
Expand All @@ -209,11 +206,8 @@ public void When_a_time_is_close_to_an_earlier_time_by_one_tick_it_should_succee
var dateTime = TimeOnly.FromDateTime(DateTime.UtcNow);
var actual = new TimeOnly(dateTime.Ticks - 1);

// Act
Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1));

// Assert
act.Should().NotThrow();
// Act / Assert
actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1));
}

[Fact]
Expand All @@ -223,11 +217,8 @@ public void When_asserting_subject_time_is_close_to_the_minimum_time_it_should_s
TimeOnly time = TimeOnly.MinValue.Add(50.Milliseconds());
TimeOnly nearbyTime = TimeOnly.MinValue;

// Act
Action act = () => time.Should().BeCloseTo(nearbyTime, 100.Milliseconds());

// Assert
act.Should().NotThrow();
// Act / Assert
time.Should().BeCloseTo(nearbyTime, 100.Milliseconds());
}

[Fact]
Expand All @@ -237,11 +228,8 @@ public void When_asserting_subject_time_is_close_to_the_maximum_time_it_should_s
TimeOnly time = TimeOnly.MaxValue.Add(-50.Milliseconds());
TimeOnly nearbyTime = TimeOnly.MaxValue;

// Act
Action act = () => time.Should().BeCloseTo(nearbyTime, 100.Milliseconds());

// Assert
act.Should().NotThrow();
// Act / Assert
time.Should().BeCloseTo(nearbyTime, 100.Milliseconds());
}

[Fact]
Expand Down Expand Up @@ -283,26 +271,224 @@ public void When_asserting_subject_time_is_close_to_an_earlier_time_by_35ms_it_s
TimeOnly time = new TimeOnly(12, 15, 31, 035);
TimeOnly nearbyTime = new TimeOnly(12, 15, 31);

// Act / Assert
time.Should().BeCloseTo(nearbyTime, 35.Milliseconds());
}

[Fact]
public void When_asserting_subject_nulltime_is_close_to_another_it_should_throw()
{
// Arrange
TimeOnly? time = null;
TimeOnly nearbyTime = new TimeOnly(12, 15, 31);

// Act
Action act = () => time.Should().BeCloseTo(nearbyTime, 35.Milliseconds());

// Assert
act.Should().NotThrow();
act.Should().Throw<XunitException>()
.WithMessage("Expected*, but found <null>.");
}
}

public class NotBeCloseTo
{
[Fact]
public void When_asserting_subject_nulltime_is_close_to_another_it_should_throw()
public void A_null_time_is_never_unclose_to_an_other_time()
{
// Arrange
TimeOnly? time = null;
TimeOnly nearbyTime = new TimeOnly(12, 15, 31);

// Act
Action act = () => time.Should().BeCloseTo(nearbyTime, 35.Milliseconds());
Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds());

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected*, but found <null>.");
.WithMessage("Did not expect*, but found <null>.");
}

[Fact]
public void When_asserting_that_time_is_not_close_to_a_negative_precision_it_should_throw()
{
// Arrange
var dateTime = TimeOnly.FromDateTime(DateTime.UtcNow);
var actual = new TimeOnly(dateTime.Ticks - 1);

// Act
Action act = () => actual.Should().NotBeCloseTo(dateTime, -1.Ticks());

// Assert
act.Should().Throw<ArgumentOutOfRangeException>()
.WithMessage("* value of precision must be non-negative*");
}

[Fact]
public void When_a_time_is_close_to_a_later_time_by_one_tick_it_should_fail()
{
// Arrange
var dateTime = TimeOnly.FromDateTime(DateTime.UtcNow);
var actual = new TimeOnly(dateTime.Ticks - 1);

// Act
Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1));

// Assert
act.Should().Throw<XunitException>();
}

[Fact]
public void When_a_time_is_close_to_an_earlier_time_by_one_tick_it_should_fail()
{
// Arrange
var dateTime = TimeOnly.FromDateTime(DateTime.UtcNow);
var actual = new TimeOnly(dateTime.Ticks + 1);

// Act
Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1));

// Assert
act.Should().Throw<XunitException>();
}

[Fact]
public void When_a_time_is_close_to_a_min_value_by_one_tick_it_should_fail()
{
// Arrange
var dateTime = TimeOnly.MinValue;
var actual = new TimeOnly(dateTime.Ticks + 1);

// Act
Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1));

// Assert
act.Should().Throw<XunitException>();
}

[Fact]
public void When_a_time_is_close_to_a_max_value_by_one_tick_it_should_fail()
{
// Arrange
var dateTime = TimeOnly.MaxValue;
var actual = new TimeOnly(dateTime.Ticks - 1);

// Act
Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1));

// Assert
act.Should().Throw<XunitException>();
}

[Fact]
public void When_asserting_subject_time_is_not_close_to_an_earlier_time_it_should_throw()
{
// Arrange
TimeOnly time = new TimeOnly(12, 15, 31, 020);
TimeOnly nearbyTime = new TimeOnly(12, 15, 31);

// Act
Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds());

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Did not expect time to be within 20ms from <12:15:31.000>, but it was <12:15:31.020>.");
}

[Fact]
public void When_asserting_subject_time_is_not_close_to_an_earlier_time_by_a_20ms_timespan_it_should_throw()
{
// Arrange
TimeOnly time = new TimeOnly(12, 15, 31, 020);
TimeOnly nearbyTime = new TimeOnly(12, 15, 31);

// Act
Action act = () => time.Should().NotBeCloseTo(nearbyTime, TimeSpan.FromMilliseconds(20));

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Did not expect time to be within 20ms from <12:15:31.000>, but it was <12:15:31.020>.");
}

[Fact]
public void When_asserting_subject_time_is_not_close_to_another_value_that_is_later_by_more_than_20ms_it_should_succeed()
{
// Arrange
TimeOnly time = new TimeOnly(12, 15, 30, 979);
TimeOnly nearbyTime = new TimeOnly(12, 15, 31);

// Act / Assert
time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds());
}

[Fact]
public void When_asserting_subject_time_is_not_close_to_another_value_that_is_earlier_by_more_than_20ms_it_should_succeed()
{
// Arrange
TimeOnly time = new TimeOnly(12, 15, 31, 021);
TimeOnly nearbyTime = new TimeOnly(12, 15, 31);

// Act / Assert
time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds());
}

[Fact]
public void When_asserting_subject_datetime_is_not_close_to_an_earlier_datetime_by_35ms_it_should_throw()
{
// Arrange
TimeOnly time = new TimeOnly(12, 15, 31, 035);
TimeOnly nearbyTime = new TimeOnly(12, 15, 31);

// Act
Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds());

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Did not expect time to be within 35ms from <12:15:31.000>, but it was <12:15:31.035>.");
}

[Fact]
public void When_asserting_subject_null_time_is_not_close_to_another_it_should_throw()
{
// Arrange
TimeOnly? time = null;
TimeOnly nearbyTime = new TimeOnly(12, 15, 31);

// Act
Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds());

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Did not expect*, but found <null>.");
}

[Fact]
public void When_asserting_subject_time_is_not_close_to_the_minimum_time_it_should_throw()
{
// Arrange
TimeOnly time = TimeOnly.MinValue.Add(50.Milliseconds());
TimeOnly nearbyTime = TimeOnly.MinValue;

// Act
Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds());

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Did not expect time to be within 100ms from <00:00:00.000>, but it was <00:00:00.050>.");
}

[Fact]
public void When_asserting_subject_time_is_not_close_to_the_maximum_time_it_should_throw()
{
// Arrange
TimeOnly time = TimeOnly.MaxValue.Add(-50.Milliseconds());
TimeOnly nearbyTime = TimeOnly.MaxValue;

// Act
Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds());

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Did not expect time to be within 100ms from <23:59:59.999>, but it was <23:59:59.949>.");
}
}

Expand Down

0 comments on commit 88e435b

Please sign in to comment.