Creating Unit Test project for NodaTime

Previous Article

What Is Noda Time?

NodaTime is an open-source date and time library for .NET applications. It is designed to provide a more robust and precise alternative to the built-in date and time types in the .NET Framework. Noda Time is particularly useful when dealing with complex time zone calculations, intervals, and different calendar systems.

What is a Unit test, and how much is it important for a developer?

A unit test is a type of software testing that focuses on validating the individual units or components of a software application in isolation. A "unit" typically refers to the smallest testable part of the software, such as a function, method, or class. Unit testing is a crucial aspect of the software development process, and its importance for developers cannot be overstated. Here are several reasons why unit testing is considered essential:

  1. Bug Detection: Unit tests help identify and catch bugs or defects in the early stages of development. By testing individual units of code, developers can quickly detect and fix issues before they propagate to higher-level components or the entire application.
  2. Code Confidence: Unit testing provides a level of confidence to developers that their code works as expected. It allows them to make changes to the codebase with the assurance that existing functionality won't break as long as the unit tests continue to pass.
  3. Code Refactoring: Unit tests facilitate code refactoring by providing a safety net. Developers can make improvements to the code structure, optimize performance, or enhance functionality, knowing that they can rely on unit tests to catch regressions if any occur.
  4. Cost Savings: Detecting and fixing bugs early in the development process is more cost-effective than addressing them later in the software lifecycle. Unit testing contributes to reducing the overall cost of software development and maintenance.
  5. Maintainability: Unit tests contribute to code maintainability by providing a safety net during maintenance activities. As developers modify or extend the code, they can rely on unit tests to quickly identify and rectify any unintended side effects.

Why the xUnit testing framework?

xUnit.net is a popular open-source unit testing framework for the .NET platform. It is widely used by developers for writing and executing unit tests in C# and other .NET languages. xUnit.net follows the xUnit architecture and shares similarities with other xUnit frameworks like JUnit and NUnit. xUnit.net is well-documented, actively maintained, and widely used in the .NET development community. It provides a clean and extensible framework for writing and running unit tests in .NET applications.

.NET ClassLibrary project for NodaTime

Let's start implementing the .NET ClassLibrary project for NodaTime types.

Step 1. First, let's create a Class library as NodaTime_classlibrary project for NodaTime types.

Install the NodaTime package/library from the Manage NuGet package or enter the below command in the package manager console.

This is the main library for NodaTime.          

Install-Package NodaTime

Install the NodaTime Serialization package/library as below in the package manager console. This library is useful when serializing the NodaTime type. 

Install-Package NodaTime.Serialization.JsonNet

Install the NodaTime Testing library for building the Unit test project.

Install-Package NodaTime.Testing

Step 2. Create an Interface as IDateTimeProvider.cs and add the code below for NodaTime types.

public interface IDateTimeProvider
{
    //represents a time zone
    //DateTimeZone TimeZone { get; }

    //class represents an instantaneous point on the timeline,         
    Instant Now { get; }

    //class represents a date and time without regard to any particular time zone or offset from UTC        
    LocalDateTime Today { get; }

    //class represents a date without regard to any specific time zone or offset from UTC
    LocalDate CurrentDate { get; }

    //class represents a time of day without regard to any specific time zone or offset from UTC
    LocalTime CurrentTime { get; }

    //class represents a date and time in a specific time zone
    ZonedDateTime CurrentDateTime { get; }

    //represents a date and time with a specific offset from UTC (Coordinated Universal Time).
    OffsetDateTime CurrentDateTimeOffset { get; }

}

Step 3. Create a class as DateTimeProvider.cs and implement the above interface.

public class DateTimeProvider : IDateTimeProvider
 {

     private readonly IClock clock;
     public static DateTimeZone TimeZone { get; private set; }
     public Instant Now => clock.GetCurrentInstant();
     public LocalDateTime Today => Now.InZone(TimeZone).LocalDateTime;
     public LocalDate CurrentDate => Now.InZone(TimeZone).Date;
     public LocalTime CurrentTime => Now.InZone(TimeZone).TimeOfDay;
     public ZonedDateTime CurrentDateTime => Now.InZone(TimeZone);
     public OffsetDateTime CurrentDateTimeOffset => Now.InZone(TimeZone).ToOffsetDateTime();

     public DateTimeProvider(IClock clock)
     {
         TimeZone = GetEasternTimeZoneId();
         this.clock = clock;
     }

     public DateTimeProvider(DateTimeZone timeZone)
     {
         TimeZone = timeZone;
         this.clock = SystemClock.Instance;
     }

     public DateTimeProvider() 
     {
         TimeZone = GetEasternTimeZoneId();
         this.clock = SystemClock.Instance;
     }

     private static DateTimeZone GetEasternTimeZoneId()
     {
         return DateTimeZoneProviders.Tzdb["America/New_York"];
     }

}

Step 4. Add a folder like ValueConverter and add the following converter classes. These converter classes will be useful when converting  NodaTime types to DataBase types. 

NodaTime ClassLibrary

a) InstantConverter: used to convert Instant type to DateTime.

public class InstantConverter : ValueConverter<Instant, DateTime>
{
    public InstantConverter(ConverterMappingHints mappingHints = null)
        : base(
            instant => InstantToDateTime(instant),
            dateTime => DateTimeToInstant(dateTime),
            mappingHints)
    {
    }

    private static DateTime InstantToDateTime(Instant instant)
    {
        return instant.ToDateTimeUtc();
    }

    private static Instant DateTimeToInstant(DateTime dateTime)
    {
        DateTime utcDateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
        return Instant.FromDateTimeUtc(utcDateTime);
    }
}

b) LocalDateConverter: this converter is used to convert the LocalDate type to the DateOnly type.

public class LocalDateConverter : ValueConverter<LocalDate, DateTime>
 {
     public LocalDateConverter(ConverterMappingHints mappingHints = null)
         : base(
             localDate => LocalDateToDateTime(localDate),
             dateTime => DateTimeToLocalDate(dateTime),
             mappingHints)
     {
     }

     private static DateTime LocalDateToDateTime(LocalDate localDate)
     {
         return localDate.AtMidnight().ToDateTimeUnspecified();
     }

     private static LocalDate DateTimeToLocalDate(DateTime dateTime)
     {
         return LocalDate.FromDateTime(dateTime);
     }
}

c) LocalDateTimeConverter: this converter is used to convert LocalDateTime to Datetime type.

public class LocalDateTimeConverter : ValueConverter<LocalDateTime, DateTime>
{
     public LocalDateTimeConverter(ConverterMappingHints mappingHints = null)
         : base(
             localDateTime => localDateTime.ToDateTimeUnspecified(),
             dateTime => LocalDateTime.FromDateTime(dateTime),
             mappingHints)
     { }
}

d) LocalTimeConverte: this converter is used to convert LocalTime to Time only type.

public class LocalTimeConverter : ValueConverter<LocalTime, TimeSpan>
{
     public LocalTimeConverter(ConverterMappingHints mappingHints = null)
         : base(
             localTime => localTime.ToTimeSpan(),
             timeSpan => LocalTime.FromTicksSinceMidnight((long)timeSpan.TotalMilliseconds * TimeSpan.TicksPerMillisecond),
             mappingHints)
     {
     }
 }

 public static class LocalTimeExtensions
 {
     public static TimeSpan ToTimeSpan(this LocalTime localTime)
     {
         return new TimeSpan(localTime.TickOfDay / NodaConstants.TicksPerMillisecond * TimeSpan.TicksPerMillisecond);
     }
}

e) OffsetDateTimeConverter: this converter is used to convert OffsetDateTime to DateTimeOffset.

public class OffsetDateTimeConverter : ValueConverter<OffsetDateTime, DateTimeOffset>
 {
     public OffsetDateTimeConverter(ConverterMappingHints mappingHints = null)
         : base(
             offsetDateTime => offsetDateTime.ToDateTimeOffset(),
             dateTimeOffset => OffsetDateTime.FromDateTimeOffset(dateTimeOffset),
             mappingHints)
     { }
}

f) ZonedDateTimeConverter: this converter is used to convert ZonedDateTime to DateTimeOffset.

public class ZonedDateTimeConverter : ValueConverter<ZonedDateTime, DateTimeOffset>
{       
    public ZonedDateTimeConverter(ConverterMappingHints mappingHints = null)
        : base(
              zonedDateTime => zonedDateTime.ToDateTimeOffset(),
              dateTimeOffset => DateTimeOffset(dateTimeOffset),// Instant.FromDateTimeOffset(dateTimeOffset).InZone(DateTimeZoneProviders.Tzdb["UTC"]),
              mappingHints)
    {
    }

    private static ZonedDateTime DateTimeOffset(DateTimeOffset dateTimeOffset)
    {
        //return Instant.FromDateTimeOffset(dateTimeOffset).InZone(DateTimeZoneProviders.Tzdb["America/New_York"]);
        return Instant.FromDateTimeOffset(dateTimeOffset).InZone(DateTimeProvider.TimeZone);
    }
}

g) ZonedDateTimeJsonConverter: this converter is used to convert ZonedDateTime to DateTimeOffset and serialize the output.

public class ZonedDateTimeJsonConverter : JsonConverter<ZonedDateTime>
{
    public override ZonedDateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        throw new NotImplementedException("Deserialization is not supported.");
    }

    public override void Write(Utf8JsonWriter writer, ZonedDateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToDateTimeOffset());
    }
}

Step 5. Build the project. It should be built successfully.

Step 6. Add the XUnit Unit test project for the above class library project and add the below code.

public class DateTimeProviderTests
{     
     [Fact]
     public void DateTimeProviderTest_ReturnsExpectedValues()
     {
         var fixedClock = new FakeClock(NodaConstants.UnixEpoch);
         var dateTimeProvider = new DateTimeProvider(fixedClock);

         DateTimeZone TestTimeZone = DateTimeProvider.TimeZone;
         var now = dateTimeProvider.Now;
         var today = dateTimeProvider.Today;
         var currentDate = dateTimeProvider.CurrentDate;
         var currentTime = dateTimeProvider.CurrentTime;
         var currentDateTime = dateTimeProvider.CurrentDateTime;
         var currentDateTimeOffset = dateTimeProvider.CurrentDateTimeOffset;

         Assert.Equal(NodaConstants.UnixEpoch, now);
         Assert.Equal(NodaConstants.UnixEpoch.InZone(TestTimeZone).LocalDateTime, today);
         Assert.Equal(NodaConstants.UnixEpoch.InZone(TestTimeZone).Date, currentDate);
         Assert.Equal(NodaConstants.UnixEpoch.InZone(TestTimeZone).TimeOfDay, currentTime);
         Assert.Equal(NodaConstants.UnixEpoch.InZone(TestTimeZone), currentDateTime);
         Assert.Equal(NodaConstants.UnixEpoch.InZone(TestTimeZone).ToOffsetDateTime(), currentDateTimeOffset);
     }
}

Step 7. Right-click and run the unit test method DateTimeProviderTest_ReturnsExpectedValues(). it should run successfully without any error.

Text explorer

The entire project structure looks as below.

NodaTime ClassLibrary

Summary

NodaTime offers a robust and flexible solution for handling date and time in .NET applications, addressing many of the limitations and ambiguities present in the standard DateTime types. Using this ClassLibrary project, we can build an organization-level package and use it within the organization. Unit testing is a fundamental practice that enhances code quality, reduces the likelihood of bugs, and supports a more efficient and collaborative development process. It is an integral part of modern software development methodologies and is considered a best practice for building robust and maintainable software.