I have been noticing lately that is really nice to use a DateTime representing 'now' as an input parameter for your methods, for mocking and testing purposes. Instead of every method calling DateTime.UtcNow
themselves, I do it once in the upper methods and forward it on the lower ones.
So a lot of methods that need a 'now', have an input parameter DateTime now
.
(I'm using MVC, and try to detect a parameter called now and modelbind DateTime.UtcNow to it)
So instead of:
public bool IsStarted
{
get { return StartTime >= DateTime.UtcNow; }
}
I usually have:
public bool IsStarted(DateTime now)
{
return StartTime >= now;
}
So my convention is at the moment, if a method has a DateTime
parameter called now
, you have to feed it with the current time. Of course this comes down to convention, and someone else can easily just throw some other DateTime in there as a parameter.
To make it more solid and static-typed I am thinking about wrapping DateTime in a new object, i.e. DateTimeNow. So in one of the most upper layers I will convert the DateTime
to a DateTimeNow
and we will get compile errors when, someone tries to fiddle in a normal DateTime.
Of course you can still workaround this, but at least if feels more that you are doing something wrong at point. Did anyone else ever went into this path? Are there any good or bad results on the long term that I am not thinking about?
You can create an interface with necessary properties. Namely IClock
and inject it as a dependancy.
interface IClock
{
DateTime Now { get; }
DateTime UtcNow { get; }
}
class SystemClock : IClock
{
public DateTime Now { get { return DateTime.Now; } }
public DateTime UtcNow { get { return DateTime.UtcNow ; } }
}
class TestDoubleClock : IClock
{
public DateTime Now { get { return whateverTime; } }
public DateTime UtcNow { get { return whateverTime ; } }
}
This way you can easily unit test your code which depends on DateTime
. Passing the DateTime.Now
everywhere as parameter sounds crappy. What if you need Now
and also UtcNow
and something else? Will you add three parameter for this purpose alone?
I suggest this interface technique to avoid ugly code with too many parameters which doesn't serves you much.
I suggest creating an interface for providing a Now value:
public interface IDateTimeProvider
{
DateTime Now { get; }
}
Then if you want to use current date in your MVC application just implement a class like this:
public class CurrentDateTimeProvider : IDateTimeProvider
{
public DateTime Now
{
get { return DateTime.Now; }
}
}
You can then inject that to your controller, mock it in unit tests and even replace with other implementation (for example if you decide to use UtcNow instead of Now in your code
Yes it can be a good idea to wrap different timer implementation in a custom type, as you mention it.
I see three drawbacks in passing the time object around:
Remember that the time accuracy in a given function will depend how how long it took to execute the code since the first call to DateTime.Now
:
var date = DateTime.Now
func_1(date) // took 1 second to execute
func_2(date) // took 1 second to execute
funf_3(date) // date is now late by 3 seconds.
You add a parameter to each of your function, and you add an indirection when getting the time (through your wrapper). In my case this overhead was noticeable in some scenarios.
Except if you allow only one concrete type to encapsulate the time, you wont be able to prevent client code to write their own Time wrapper, still giving whatever time they want to your functions.
I usually tackle this issue by letting the class accept a nowProvider
as part of the contstructor like this:
public class ClassToTest
{
private readonly Func<DateTime> _nowProvider;
public ClassToTest(Func<DateTime> nowProvider)
{
_nowProvider = nowProvider;
}
public ClassToTest()
{
_nowProvider = () => DateTime.Now;
}
//snip
}
So in my tests I can do this:
var knownDate = new DateTime(2000, 1, 1);
var testObject = new ClassToTest(() => knownDate);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With