Back to all posts

Unit Testing .NET Core When Using Common Statics (DateTime, Folder, File, etc.)

Posted on Jan 18, 2020

Posted in category:
Development
ASP.NET

Writing software in a unit testable manner is sometimes more complicated than we want it to be. The complexity of writing testable code is typically exacerbated by the architecture of fundamental components of .NET Core. When working with users that encounter these difficulties, I usually see one of three reactions; those that skip testing those methods, those that "hack" a solution to test around a limitation, and those that take the time to write truly testable code. To this point, I've started to publish a free library of utilities that helps to bridge this gap!

A Typical Example

One of the simplest examples to understand that highlights the problems mentioned above is the usage of DateTime.Now, or any other property of the DateTime object. Consider the following Factory class.

Sample Customer Factory
public class CustomerFactory
{
    public Customer CreateCustomer()
    {
        return new Customer
        {
            CreateDate = DateTime.Now
        };
    }
}

Nothing here seems at all out of the ordinary, however, when we come to try and write a unit test we will see a problem. Consider the following Unit Test.

Unit Test - Will Not Succeed
public class CustomerTests
{
    private readonly CustomerFactory _factory = new CustomerFactory();

    [Fact]
    public void CreateCustomer_ShouldDefaultCreateDateToToday()
    {
        //Arrange
        var expected = DateTime.Now;

        //Act
        var result = _factory.CreateCustomer();

        //Assert
        Assert.Equal(expected, result.CreateDate);
    }
} 

Again, this all looks innocent enough, until we run the code. Since we are calling DateTime.Now inside of our test to get the expected, and inside the actual method, we get the value it will be different, resulting in the Assert failing. After noticing the failure, we will see the typical developer start to "solution" around the issue. You could create a Shim/Wrapper around the actual DateTime method, which would be the most appropriate, others might try to allow a reasonable offset, which would be horrible, etc.

Introducing ICG.AspNetCore.Utilities

After writing the same code in multiple projects to wrap common classes such as DateTime, File, Folder, and the like, I created a free NuGet package that contains valuable objects, that can be injected to a product allowing things to be tested easily.

Installation

Installation of these utilities is done in two simple steps; the addition of a NuGet package and registering of the objects for Dependency Injection in startup. cs.

NuGet Install
Install-Package ICG.AspNetCore.Utilities
Addition to Startup.cs
services.UseIcgAspNetCoreUtilities();

Once this is done, it is easy to modify the code from the prior example to use the various wrapper objects provided.

Usage

Simply inject the needed wrapper into your class, the CustomerFactory from above would be updated as follows.

Updated CustomerFactory Sample
public class CustomerFactory
{
    private readonly ITimeProvider _timeProvider;

    public CustomerFactory(ITimeProvider timeProvider)
    {
        _timeProvider = timeProvider;
    }

    public Customer CreateCustomer()
    {
        return new Customer
        {
            CreateDate = _timeProvider.Now
        };
    }
}

We have taken careful consideration to copy all Microsoft Help documentation for all methods/overloads to the user experience will be similar. However, when writing unit tests you can now properly test.

Once we have the method updated, we can now write a Unit Test that will succeed as below. The below code does expect the inclusion of the Moq library for the creation of Mock objects.

Updated Unit Test
public class CustomerTests
{
    private readonly Mock _timeProviderMock;
    private readonly CustomerFactory _factory;

    public CustomerTests()
    {
        _timeProviderMock = new Mock();
        _factory = new CustomerFactory(_timeProviderMock.Object);
    }

    [Fact]
    public void CreateCustomer_ShouldDefaultCreateDateToToday()
    {
        //Arrange
        var expected = DateTime.Now;
        _timeProviderMock.Setup(t => t.Now).Returns(expected);

        //Act
        var result = _factory.CreateCustomer();

        //Assert
        Assert.Equal(expected, result.CreateDate);
        _timeProviderMock.Verify(t => t.Now, Times.Once);
    }
}

As you can see from above, we are now able to set an expected time, still using DateTime.Now, but we can control the setup of the TimeProvider to ensure that when it is called we return the value we expect. We can even further verify that our code inside of CreateCustomer calls our code.

Going Forward

This is a simple shift in programming style, but can result in substantially easier code to modify. The ICG.AspNetCore.Utilities project is 100% Open Source and my team is constantly working on the project to add more items like this. Feel free to submit suggestions either as issues or Code Contributions. It is my goal to make unit testing easier for everyone and to prevent everyone from writing the same boilerplate code day after day. I hope this helps!