Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock DateTime.Now in unit tests? [closed]

The normal solution is to hide it behind interface.

public class RecordService
{
    private readonly ISystemTime systemTime;

    public RecordService(ISystemTime systemTime)
    {
        this.systemTime = systemTime;
    }

    public void RouteRecord(Record record)
    {
        if (record.Created < 
            systemTime.CurrentTime().AddMonths(-2))
        {
            // process old record
        }

        // process the record
    }
}

In the unit test you can use mock object and decide what to return

[TestClass]
public class When_old_record_is_processed
{
    [TestMethod]
    public void Then_it_is_moved_into_old_records_folder()
    {
        var systemTime = A.Fake<ISystemTime>();
        A.CallTo( () => system.Time.CurrentTime())
          .Returns(DateTime.Now.AddYears(-1));

        var record = new Record(DateTime.Now);
        var service = new RecordService(systemTime);

        service.RouteRecord(record);

        // Asserts...
    }
}

I don’t like to inject another interface into my class just to get the current time. It feels too heavy solution for a such a small problem. The solution is to use static class with public function.

public static class SystemTime
{
    public static Func<DateTime> Now = () => DateTime.Now;
}

Now we can remove the ISystemTime injection and RecordService looks like this

public class RecordService
{
    public void RouteRecord(Record record)
    {
        if (record.Created < SystemTime.Now.AddMonths(-2))
        {
            // process old record
        }

    // process the record
    }
}

In the unit tests we can mock the system time just as easily.

[TestClass]
public class When_old_record_is_processed
{
    [TestMethod]
    public void Then_it_is_moved_into_old_records_folder()
    { 
        SystemTime.Now = () => DateTime.Now.AddYears(-1);
        var record = new Record(DateTime.Now);
        var service = new RecordService();

        service.RouteRecord(record);

        // Asserts...
    }
}

Of course there is a downside to all this. You are using public fields (The HORROR!) so nobody is stopping you writing code like this.

public class RecordService
{
    public void RouteRecord(Record record)
    { 
        SystemTime.Now = () => DateTime.Now.AddYears(10);
    } 
}

Also I think it is better to educate developers than create abstractions just to protect them from doing any mistakes. Other possible issues are related to running the tests. If you forget to restore the function back to it's original state it might affect other tests. This depends on the way unit test runner executes the tests. You can use the same logic to mock file system operations

public static class FileSystem
{
    public static Action<string, string> MoveFile = File.Move;
}

In my opinion implementing this kind of functionality (mocking time, simple file system operations) using public functions is perfectly acceptable. It makes the code easier to read, decreases the dependencies and it is easy to mock in the unit tests.

like image 720
Unknown Avatar asked Mar 30 '12 11:03

Unknown


People also ask

Can you mock DateTime now?

The wrapper implementation will access DateTime and in the tests you'll be able to mock the wrapper class. Use Typemock Isolator, it can fake DateTime. Now and won't require you to change the code under test. Use Moles, it can also fake DateTime.

Does unit testing save time?

Unit testing results in quality software This ensures a reliable engineering environment where quality is paramount. Over the course of the product development life cycle, unit testing saves time and money, and helps developers write better code, more efficiently.

How do you skip a unit test method?

It is possible to skip individual test method or TestCase class, conditionally as well as unconditionally. The framework allows a certain test to be marked as an 'expected failure'. This test will 'fail' but will not be counted as failed in TestResult. Since skip() is a class method, it is prefixed by @ token.


1 Answers

You do not need to implement this by hand. You can use Moles framework to do this. channel 9

like image 86
daryal Avatar answered Sep 28 '22 06:09

daryal