8

How would you test a method that is time-dependent?

A simple example could be:

public static int GetAge(DateTime birthdate)
{
    // ...
}

[TestMethod]
public void GetAgeTest()
{
    // This is only ok for year 2013 !
    Assert.AreEqual(GetAge(new DateTime(2000, 1, 1)), 13);
}

The problem is that it will work only for the current year (2013 in this case). As soon as a new year begins, I have to rewrite all time-dependent tests...

What is the best approach to Unit Test such a method?

Bidou
  • 7,378
  • 9
  • 47
  • 70
  • Check out VitualTime , it simplifies unit testing time dependent apps, examples here https://stackoverflow.com/questions/2425721/unit-testing-datetime-now/49374431#49374431 – Samuel Jun 18 '18 at 01:41

8 Answers8

4

You could override the mechanism by which your GetAge method finds today's date.

If you wrap DateTime in another class you can then replace the actual calls with fake calls that return known values while unit testing.

You can use dependency injection to switch between the real implementation for production and the fake implementation for testing.

ChrisF
  • 134,786
  • 31
  • 255
  • 325
  • I tried and it worked; however, it seems to me that this adds too much overhead. Furthermore, to inject the dependency, I cannot use a `static` method which is a bit annoying for a static helper... – Bidou Jul 03 '13 at 19:09
  • @Bidou. Yes it is an overhead for a relatively simple test - but would come into it's own for more complex ones. – ChrisF Jul 03 '13 at 20:59
  • 4
    @Bidou, it does add some awkwardness. It brings up an interesting question, though, which is why are the objects being tested dependent on timing information in the first place? Should the GetAge method really implicitly rely on DateTime.Now or would it make more sense for it to accept the reference DateTime as a parameter? The difficulty of testability here is indicative of (unnecessary?) dependency that might be removed with a slightly different design. – Dan Bryant Jul 03 '13 at 21:10
3

Also, don't forget the "Expected" argument is the first one in AreEquals.

[TestMethod]
public void GetAgeTest()
{
    int age = 13;
    Assert.AreEqual(age, GetAge(DateTime.Now.AddYears(age * -1));
}

Would also be better to split the test in Arrange - Act - Assert groups like this :

[TestMethod]
public void GetAgeTest()
{
    // Arrange
    int age = 13;

    // Act
    int result = GetAge(DateTime.Now.AddYears(age * -1);

    // Assert
    Assert.AreEqual(age, result);
}
Pierre-Luc Pineault
  • 8,993
  • 6
  • 40
  • 55
  • Ditto on this as with Claudio's answer; a rare race condition can cause this test to fail. This is a fundamental problem with testing methods that rely directly on the current time. – Dan Bryant Jul 03 '13 at 13:20
  • @DanBryant Even if with crazy luck the automated test suite fails once on New Year's Eve, I don't think anyone will be at work to see it anyway haha – Pierre-Luc Pineault Jul 03 '13 at 13:23
2

Two main options:

  1. Create a service that isolates time-dependent functionality and inject its interface into classes that need it. Then you can mock this interface to get the behavior you need for testing.

  2. Use a detouring test framework like Moles (there are a few others out there, as well) that can reroute static method calls like DateTime.Now.

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
  • Moles seems to be interesting, I found an example here: http://stackoverflow.com/questions/5961982/using-moles-with-datetime Unfortunately, Mock Framework like Moq do not support routing or moqing static method/property like `DateTime.Now` – Bidou Jul 03 '13 at 19:13
1

Something like this would do the trick

var birthDate = new DateTime(DateTime.Now.Year - 13, 1, 1)
int age = GetAge(birthDate);
Assert.AreEqual(age, 13);
Claudio Redi
  • 67,454
  • 15
  • 130
  • 155
  • 1
    This test can fail when executed on New Year's Eve if the timing works out just right at the year changeover. – Dan Bryant Jul 03 '13 at 13:18
  • @Dan Bryant: that's a really really extreme case... 1 in 100.000.000... or 1 in 1000.000.000.000 ... I personally wouldn't worry about that since it's almost impossible to happen, but it's a personal opinion – Claudio Redi Jul 03 '13 at 13:20
  • 1
    @Dan: Well spotted! You can avoid that problem by drinking a glass of champagne instead of running your unit tests at the critical time ;-) – Treb Jul 03 '13 at 13:21
  • 1
    @Claudio, it might not be as rare of a case as you'd think; a lot of automated build servers are configured to run late at night and they generally don't take holidays. This is also a deliberately simplified example. More complex operations that occur over longer periods of time can exhibit more complex failure cases, such as leap-year effects, daylight savings effects, etc. These corner cases often require testing and you can't reproduce them accurately without having full control over the program's measurement of time. – Dan Bryant Jul 03 '13 at 13:23
  • @DanBryant: I know CI servers will run this code automatically and, even in this case, chances are 1 in 1000.000.000.000. I appreciate your feedback but in my opinion you're overthinking this particular case. – Claudio Redi Jul 03 '13 at 13:29
1

Another solution would be to use Microsoft Shimes and Fakes. Look at http://msdn.microsoft.com/en-us/library/hh549175.aspx

using (ShimsContext.Create())
{
    // Shim DateTime.Now to return a fixed date:
    System.Fakes.ShimDateTime.NowGet = () =>
    { 
        return new DateTime(fixedYear, 1, 1); 
    };
}
Bidou
  • 7,378
  • 9
  • 47
  • 70
0

Maybe not the best option in this specific case, but you could also just extend the interface of your function to also required a DateTime now to be passed.

public static int GetAge(DateTime birthdate, DateTime now)
{
    // ...
}

[TestMethod]
public void GetAgeTest()
{
    Assert.AreEqual(GetAge(new DateTime(2000, 1, 1), new DateTime(2013, 1, 1)), 13);
}

You're basically delegating the responsibility of getting the current date to the caller (also pushing the unit test problem there though).

A nice side effect is that you really get a well defined "now" within the bounds of this function. On the ticks level, the moment of calling DateTime.Now would make a difference. You can describe the function on an abstract level as "given this birthdate and this current date, the age is".

Again, this might not be the best way to go in every situation, but I think it's worth considering in some.

Matthijs Wessels
  • 6,530
  • 8
  • 60
  • 103
0

Another solution is to use VirtualTime. , it simplifies unit testing time dependent apps, see how to use and examples here Unit Testing: DateTime.Now

Samuel
  • 1,295
  • 16
  • 21
0

You can consider time as a dependency and inject it into your method. I suggest using the IClock interface from the NodaTime. Then in your unit tests, you can use the FakeClock class from NodaTime.Testing.

Your modified method.

public static int GetAge(IClock clock, DateTime birthdate)
{
    now = clock.GetCurrentInstant().ToDateTimeUtc();
    // use now and birthdate to calculate age
}

Then you can write your unit test like this.

[TestMethod]
public void GetAgeTest()
{
    var clock = new FakeClock(Instant.FromUtc(2013, 1, 1, 0, 0, 0)); // Create a fake clock that always return 2013/01/01 0:00:00
    var birthDate = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    Assert.AreEqual(GetAge(clock, birthdate), 13); Always return 13
}

If you ever decide to move GetAge into its own class.

public class AClass
{
    private readonly IClock _clock;

    public AClass(IClock clock) => _clock = clock;

    public int GetAge(DateTime birthdate)
    {
        now = _clock.GetCurrentInstant().ToDateTimeUtc();
        // use now and birthdate to calculate age
    }
}

The FakeClock is a very powerful testing tool. You can read more about it from my blog post here.

duongntbk
  • 620
  • 4
  • 25