As professional developers, we are all striving to provide 100% automated test coverage for all code we write. However, one particular class of features is quite difficult to test - those that depend on current time, in one way or another. For example, an expiring token - how we test that it actually expires in 20 minutes? In this article, I share a simple technique that helps with this challenge, that I have been personally using for quite some time. As an extra bonus, I will also share the 'simplest' stopwatch utility methods.
The Time Testing Problem and a Solution
Let's start with a simple case that often comes up in practice. Let's say you have a simple object - a Token
- that has a lifetime, so its state is dependent on the current time. The token is valid immediately after it is created, but it expires after 20 minutes:
public class Token {
DateTime _created;
public Token() {
_created = DateTime.UtcNow;
}
public bool IsValid() {
return DateTime.UtcNow < _created.AddMinutes(20);
}
}
Quite simple code, but real-life tokens might be a lot more complex. Now the question is - how to unit test it? Straight solution:
var token = new Token();
Assert.IsTrue(token.IsValid());
Thread.Sleep(21 * 60 * 1000);
Assert.IsFalse(token.IsValid());
This is obviously beyond ridiculous. Nobody would accept a 'test' that hangs for 20 minutes. We can move the constant 20 (for minutes) to some settings object (which we should do anyway), and then try to set much lower value for testing, but this does not eliminate the problem completely. And in real life applications, with many settings, many time-related constants, it might become hard to do, and it also introduces risk of significantly altering testing environment (what we test vs what we run in production).
In manual testing, for an entire application, QA engineers sometimes use workarounds. Let's say this token is an access token produced at login. The tester logs-in, checks that she can access the app, then quickly changes the current time on the computer (moves it forward 30 minutes); and tries the app again. The token should 'expire' and the app should not be available. This is a race against the time service on the machine, which regularly sets the correct time using network service. Anyway, this is tricky, and does not work for unit testing anyway.
So how to make it work? The idea is to create a wrapper for DateTime.UtcNow
that allows 'shifting' the current time for an application. And I already see some raised eyebrows - what? You suggest to modify the app to make your test run? Well, not exactly. We do not change the application logic. We just make this logic testable. I think there is nothing wrong with this approach.
So let's introduce a new AppTime static
class that will serve us as a new API to access the current time:
public static class AppTime {
private static TimeSpan _offset = TimeSpan.Zero;
public static DateTime UtcNow {
get {
var now = DateTime.UtcNow;
return (_offset == TimeSpan.Zero) ? now : now.Add(_offset);
}
}
public static void SetOffset(TimeSpan offset) {
_offset = offset;
}
public static void ClearOffset() {
_offset = TimeSpan.Zero;
}
}
The AppTime
provides access to current UTC time, but you can introduce a permanent shift in returned values. This is effectively a time machine, which allows shifting 'application time' at will.
The Token
class code now changes to:
public class Token {
DateTime _created;
public Token() {
_created = AppTime.UtcNow;
}
public bool IsValid() {
return AppTime.UtcNow < _created.AddMinutes(20);
}
}
and testing becomes trivial:
var token = new Token();
Assert.IsTrue(token.IsValid());
AppTime.SetOffset(TimeSpan.FromMinutes(21));
Assert.IsFalse(token.IsValid());
AppTime.ClearOffset();
I want to note one important and subtle point here. What happens if the second assertion fails, from a bug in Token.IsValid()
method? The Assert
will throw exception, and the last ClearOffset()
will not be executed. Seems like not a big deal, but if you are running a battery of tests, some other time-dependent tests executing AFTER this failed test will fail too - because they start with already shifted time. You will see a whole bunch of failed tests, with no clue that the original cause was this single faulty method.
To avoid this, always place AppTime.ClearOffset() into Test-cleanup method of the test class; this method executes after every test, and will guarantee tests isolation from each other.
The general strategy is the following - everywhere in your application and libraries you use AppTime.UtcNow
, not DateTime.UtcNow
. This will provide you with a time machine for the entire application. The AppTime
class, full listing with comments is provided with the download zip for this article.
Using Apptime in Manual Testing
It may seem at first that usefulness of this technique is limited to code-based, automated testing only. Manual testing, through UI clicking - the tester does not have an access to AppTime
class on the server, so how can she use it? There is actually a way.
Let's go back to login token example. The QA person tries to test login and login-expiration feature in a Web ASP.NET Core app. She logs-in and then tries to check that the access token expires in 20 minutes. You, the developer, can provide a secret URL, enabled only in test environment, with a controller method behind it that allows to set the time offset to a value specified in URL parameter in some form (as well another option to clear the offset). So the QA engineer logs-in, opens another tab in browser, hits the time-offset URL shifting time forward by 30 minutes. Then goes back to the app tab and tries to click something - she should be rejected, login token expired. Test passed!
Bonus - Time-Measurement Methods in Apptime Class
The AppTime
class provides a few convenience methods that simplify measuring the precise execution time of a code span. Here is a typical code fragment that does execute-and-measure-time sequence:
var sw = new Stopwatch();
sw.Start();
DoStuff();
sw.Stop();
var opTime = sw.Elapsed;
The AppTime
class provides utility methods that simplify this a bit:
var start = AppTime.GetTimestamp();
DoStuff();
var opTime = AppTime.GetDuration(start);
Turns out the Stopwatch
class has a few static
methods, and you can measure the time interval using just these methods, with sub-millisecond precision, without creating an instance of the Stopwatch
.
Of course, not a big deal of improvement - 2 lines instead of 4 in original version. But what I find attractive in the second version is that it seems less mentally distracting in the context of the method. When we read the code, we want to stay focused on the main function, what method is doing; time-measuring aspect is important, but it is essentially a side aspect, monitoring code. And the less distracting this sideline code is - the better. In the original code, having a 'new
' operator immediately grabs attention and you have to spend a split-second mental effort to realize that it is for time measurement, not mainstream functionality. With AppTime
method, it seems immediately clear what it is, so it is quickly discarded. This is, of course, a matter of personal perception and even taste. Anyway, use it if you like it.
Using the Code
The attached code is a very simple console app, with full listing of the AppTime
class.
Points of Interest
We used UtcNow
in all our examples (UTC time). The DateTime
class also provides Now
property (local time), and the AppTime
class has it too (shifted local time). However, I would strongly discourage you from using these properties, ever. Most of our code works on the servers, and the code should NOT depend on server's location/time. Servers in the cloud are always on UTC time, no matter physical location. Use UTC time, so that even when you run/test on your development laptop, your code works the same as on production server. To sum it up: stay away from local time, unless you absolutely have to use it; the cases mostly should be limited to showing the date-time values in UI.
History
- 17th October, 2020: Initial version