Skip to content

Within throws ArgumentException if null value is involved #4400

Description

@BrightLight

We tried to compare Nullable<DateTime> with a tolerance using Within. If the actual value is null, we get an ArgumentException.

Example:

[TestCase("2023-01-01 13:00:10", "2023-01-01 13:00:00", 10)] // succeeds
[TestCase(null, "2023-01-01 13:00:00", 10)] // throws
[TestCase(null, null, 10)] // succeeds
public void DatesShouldBeTheSame(DateTime? value, DateTime? expectedDateTime, int toleranceInSeconds)
{
  Assert.That(value, Is.EqualTo(expectedDateTime).Within(toleranceInSeconds).Seconds);
}

Callstack:

System.ArgumentException : Both arguments must be DateTime, DateTimeOffset or TimeSpan
   at NUnit.Framework.Constraints.DateTimes.Difference(Object x, Object y)
   at NUnit.Framework.Internal.TextMessageWriter.WriteDifferenceLine(Object expected, Object actual, Tolerance tolerance)
   at NUnit.Framework.Internal.TextMessageWriter.DisplayDifferences(Object expected, Object actual, Tolerance tolerance)
   at NUnit.Framework.Constraints.EqualConstraintResult.DisplayDifferences(MessageWriter writer, Object expected, Object actual, Int32 depth)
   at NUnit.Framework.Constraints.EqualConstraintResult.WriteMessageTo(MessageWriter writer)
   at NUnit.Framework.Assert.ReportFailure(ConstraintResult result, String message, Object[] args)
   at NUnit.Framework.Assert.That[TActual](TActual actual, IResolveConstraint expression, String message, Object[] args)
   at NUnit.Framework.Assert.That[TActual](TActual actual, IResolveConstraint expression)
   at MyUnitTests.DatesShouldBeTheSame(Nullable`1 value, Nullable`1 expectedDateTime, Int32 toleranceInSeconds) in ...

The exception happens when the "Off by:" is calculated. It seems DateTimes.Difference does not consider/expect that one of the values might be null.

internal static object Difference(object? x, object? y)
{
    if (x is DateTime xDateTime && y is DateTime yDateTime)
        return (xDateTime - yDateTime).Duration();

    if (x is TimeSpan xTimeSpan && y is TimeSpan yTimeSpan)
        return (xTimeSpan - yTimeSpan).Duration();

    if (x is DateTimeOffset xDateTimeOffset && y is DateTimeOffset yDateTimeOffset)
        return (xDateTimeOffset - yDateTimeOffset).Duration();

    throw new ArgumentException("Both arguments must be DateTime, DateTimeOffset or TimeSpan");
}

Is this the intended behavior? It would be nice if Within could support DateTime? as well. The message could simply omit the "Off by:" part and just read

Expected: 2023-01-01 13:00:00 +/- 00:00:10
  But was:  null

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions