Skip to content

Method to assert the millisecond part of a DateTime / DateTimeOffset instance #2738

@Zeruxky

Description

@Zeruxky

Background and motivation

Currently, FluentAssertions has methods to assert the year, month, day, hour, minute, second and offset of a DateTimeOffset (HaveX), but an equivalent method for the millisecond part is missing. Generally, it would be nice to assert the millisecond part in the style of FluentAssertions instead of using result.Millisecond.Should().Be(milliseconds).

I'm currently working on a library that provides convenience methods based on DateTime/DateTimeOffset and implement date and time related unit tests for it.

This is very easy to implement, because we can extend the DateTimeAssertions class and DateTimeOffsetAssertions class by a method called HaveMillisecond.

The implementation of this method could look like following:

public AndConstraint<DateTimeOffsetAssertions> HaveMillisecond(int expectedMilliseconds, string because = "", params object[] becauseArgs)
{
    Execute.Assertion
      .ForCondition(this.Subject != null)
      .BecauseOf(because, becauseArgs)
      .FailWith("Expected {context:DateTimeOffset} to have milliseconds {0}{reason}, but found <null>.", expectedMilliseconds);

    Execute.Assertion
      .ForCondition(this.Subject!.Value.Millisecond == expectedMilliseconds)
      .BecauseOf(because, becauseArgs)
      .FailWith("Expected {context:DateTimeOffset} to have milliseconds {0}{reason}, but found {1}.", expectedMilliseconds, this.Subject.Value.Millisecond);

    return new AndConstraint<DateTimeOffsetAssertions>(this);
}

Also the opponent of HaveMillisecond, which would be NotHaveMillisecond, can be implemented this way by inverting / changing the condition to this.Subject!.Value.Millisecond != expectedMilliseconds.

API Proposal

public class DateTimeOffsetAssertions<TAssertions>
{
    public AndConstraint<DateTimeOffsetAssertions> HaveMillisecond(int expectedMilliseconds, string because = "", params object[] becauseArgs);
    public AndConstraint<DateTimeOffsetAssertions> NotHaveMillisecond(int expectedMilliseconds, string because = "", params object[] becauseArgs);
}
public class DateTimeAssertions<TAssertions>
{
    public AndConstraint<DateTimeAssertions> HaveMillisecond(int expectedMilliseconds, string because = "", params object[] becauseArgs);
    public AndConstraint<DateTimeAssertions> NotHaveMillisecond(int expectedMilliseconds, string because = "", params object[] becauseArgs);
}

API Usage

var dateTimeOffset = DateTimeOffset.Parse("2024-08-23T00:00:00.123+00:00");
dateTimeOffset.Should().HaveMilliseconds(123);
dateTimeOffset.Should().NotHaveMilliseconds(124);

Alternative Designs

Alternately, you could write an extension method based on DateTimeOffsetAssertions class and add there the HaveMillisecond method. This would not directly change the API of FluentAssertions, but would not be consistent with the rest of the DateTime/DateTimeOffset method provided by FluentAssertions.

The extension method could be implemented like following:

internal static class DateTimeOffsetAssertionsExtensions
{
    internal static FluentAssertions.Primitives.DateTimeOffsetAssertions HaveMillisecond(
        this FluentAssertions.Primitives.DateTimeOffsetAssertions assertions,
        int expectedMilliseconds,
        string because = "",
        params object[] becauseArgs)
        {
            var subject = assertions.Subject;
            Execute.Assertion
                .ForCondition(subject != null)
                .BecauseOf(because, becauseArgs)
                .FailWith("Expected {context:DateTimeOffset} to have milliseconds {0}{reason}, but found <null>.", expectedMilliseconds);

            Execute.Assertion
                .ForCondition(subject!.Value.Millisecond == expectedMilliseconds)
                .BecauseOf(because, becauseArgs)
                .FailWith("Expected {context:DateTimeOffset} to have milliseconds {0}{reason}, but found {1}.", expectedMilliseconds, subject.Value.Millisecond);

            return assertions;
        }
}

Risks

None, because it adds an extra method to assert the millisecond part of a DateTime / DateTimeOffset instance.

Are you willing to help with a proof-of-concept (as PR in that or a separate repo) first and as pull-request later on?

Yes, please assign this issue to me.

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved, it can be implemented

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions