Dealing with time in tests

One of the more annoying things to test is time sensitive code.

I just spent five minutes trying to figure out why this code if failing:

repository.ResetFailures(failedMsgs);
var msgs = repository.GetAllReadyMessages();
Assert.AreEqual(2, msgs.Length);

Reset failures will set the retry time of the failed messages to 2 seconds in the features. GetAllReadyMessages will only get messages that are ready now.

Trying to test that can be a real pain. One solution that I have adopted across all my projects is introducing a separate concept of time:

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

Now, instead of calling DateTime.Now, I make the call to SystemTime.Now(), and get the same thing. This means that I can now test the code above easily, using:

SystemTime.Now = () => new DateTime(2000,1,1);
repository.ResetFailures(failedMsgs); 
SystemTime.Now = () => new DateTime(2000,1,2);
var msgs = repository.GetAllReadyMessages(); 
Assert.AreEqual(2, msgs.Length);

This is a really painless way to deal with this issue.

Print | posted on Monday, July 07, 2008 6:39 AM

Feedback


Gravatar

# re: Dealing with time in tests 7/7/2008 8:52 AM Steve

I guess this is similar to introducing a mockable IClock dependency which I have done in the past to time sensitive services.

Just curious, how do you enforce that you use the SystemTime in the actual implementation when required instead of absent-mindedly reverting to DateTime?


Gravatar

# re: Dealing with time in tests 7/7/2008 9:07 AM James

This solution seems to be the most effective way to get reliable tests if there are timing issues involved - We found this out the hard way, a lot of what our products do are timing sensitive, and require actions like backing off for X amount of time, scheduling Y to occur after Z amount of time has elapsed.

Steve - You could probably use aspects in your debug builds to intercept calls to DateTime.Now, and throw exceptions if that is the case.


Gravatar

# re: Dealing with time in tests 7/7/2008 9:15 AM Ayende Rahien

I don't try to enforce that.
The tests will make sure that this happen, because if there is time used there, it will be tested, and for that you need SystemTime.


Gravatar

# re: Dealing with time in tests 7/7/2008 1:16 PM Joshua McKinney

I've seen IClock.GetCurrentTime in the codecampserver source. Easy to stub / mock with a class that allows you to specify a time.

I curious as to why you needed to pull out the Func-y lambda magic just to set a static property here?


Gravatar

# re: Dealing with time in tests 7/7/2008 1:32 PM Ayende Rahien

Because it is a field that contains a delegate.
That allows me to directly switch values without needs to stub it.


Gravatar

# re: Dealing with time in tests 7/7/2008 3:57 PM Peter Ritchie

The problem with not doing a full injectionable implementation (i.e. some IDateTime interface, etc.) is that you're now coupled to a different class. While this new class is more flexible, it's not thread safe--one thread could change the Now delegate and affect all other threads using SystemTime.Now.


Gravatar

# re: Dealing with time in tests 7/7/2008 7:25 PM Tuna Toksoz

Why wouldn't you go for abstraction? The simplicity?
I think that this would be more readable and maybe elegant?

using(IDisposable time=Time.Frozen("11.11.2011"))
{

}


Gravatar

# re: Dealing with time in tests 7/7/2008 10:38 PM Ayende Rahien

This is not something that you'll likely change for production, it is needed for tests only.
It is also something that I _want_ to be cross threaded, I have mutli threaded tests


Gravatar

# re: Dealing with time in tests 7/7/2008 10:47 PM Ayende Rahien

Tuna,
My way is three lines of code, that is it.


Gravatar

# re: Dealing with time in tests 7/8/2008 3:50 PM Frank Quednau

Pff, And there was me calling kernel32.SetLocalTime for such tests. ;)
Thanks for the approach!


Gravatar

# re: Dealing with time in tests 7/11/2008 10:29 AM Greg Beech

So how do you test that some production code doesn't accidentally do something like:

SystemTime.Now = () => someFixedDate;

Having a read/write property for the time is dangerous if you depend on it to make decisions. And it's virtually impossible to test that you're not doing something like this anywhere it a system.


Gravatar

# re: Dealing with time in tests 7/11/2008 11:39 AM Ayende Rahien

Greg,
I don't. It doesn't make sense to do it for production.
I am not trying to verify that people can't do stupid stuff


Gravatar

# re: Dealing with time in tests 7/15/2008 8:45 AM Erich Eichinger

Greg,
you can easily add a "readOnly" flag to SystemTime and allow for modifying Now only in case "readOnly" is false. For testing purposes you can change "readOnly" in your fixture setup. Thus working around this guard would be quite obvious in the code and requires extra effort by a developer.


Gravatar

# re: Dealing with time in tests 7/31/2008 10:22 PM Anand

Ayende,

I might be too sleepy while reading this but the approach with the "Using" block (that Tuna mentions) ensures that unit tests clear up the DateTime they set for testing in the Dispose() back to DateTime.Now.

In your case you would have to use a teardown or set it back explicitly.

Comments have been closed on this topic.