When you are working on a feature that depends on time... How do you organize unit testing ? When your unit tests scenarios depend on the way your program interprets "now", how do you set them up ?
Second Edit: After a Couple of days reading your experience
I can see that the techniques to deal with this situation usually revolve around one of these three principles:
- Add (hardcode) a dependency: add a small layer over the time function/object, and always call your datetime function through this layer. This way, you can get control of time during the test cases.
- Use mocks: your code stays exactly the same. In your tests, you replace the time object by a fake time object. Sometimes, the solution involves modifying the genuine time object that is provided by your programming language.
- Use dependency injection: Build your code so that any time reference is passed as a parameter. Then, you have control of the parameters during the tests.
Techniques (or libraries) specific to a language are very welcome, and will be enhanced if an illustrative piece of code comes along. Then, most interesting is how similar principles may be applied on any platform. And yes... If I can apply it straight away in PHP, better than better ;)
Let's start with a simple example: a basic booking application.
Let's say we have JSON API and two messages: a request message, and a confirmation message. A standard scenario goes like this:
- You make a request. You get a response with a token. The resource that is needed to fulfill that request is blocked by the system for 5 minutes.
- You confirm a request, identified by the token. If token was issued within 5 minutes, it will be accepted (the resource is still available). If more than 5 minutes have passed, you have to make a new request (the resource was freed. You need to check for its availability again).
Here come the corresponding test scenario:
I make a request. I confirm (immediately) with the token I received. My confirmation is accepted.
I make a request. I wait 3 minutes. I confirm with the token I received. My confirmation is accepted.
I make a request. I wait 6 minutes. I confirm with the token I received. My confirmation is rejected.
How can we get to program these unit tests ? What architecture should we use so that these functionalities remain testable ?
Edit Note: When we shoot a request, the time is stored in a database in a format that looses any information about milliseconds.
EXTRA - but maybe a bit verbose: Here come details about what I found out doing my "homework":
I Built my feature with a dependency on a time function of my own. My function VirtualDateTime has a static method get_time() that I call where I used to call new DateTime(). This time function allows me to simulate and control what time is "now", so that I can build tests like: "Set now to 21st jan 2014 16h15. Make a request. Move ahead 3 minutes. Make confirmation.". This works fine, at the cost of the dependency, and "not so pretty code".
A little more integrated solution would be to build a myDateTime function of my own that extends DateTime with additional "virtual time" functionnalities (most importantly, set now to what I want). This is making the first solution's code slightly more elegant (use of new myDateTime instead of new DateTime), but ends up to be very similar: I have to build my feature using my own class, thus creating a dependency.
I have though about hacking the DateTime function, to make it work with my dependency when I need it. Is there any simple and neat way to replace a class by another, though ? (I think I got an answer to that: See namespace below).
In a PHP environment, I read "Runkit" may allow me to do this (hack the DateTime function) dynamically. What is nice is that I would be able to check I am running in test environment before modifying anything about DateTime, and leave it untouched in production . This sounds much safer and cleaner than any manual DateTime hack.
Dependency injection of a clock function in each class that uses time . Is this not overkill ? I see that in some environments it gets very usefull . In this case, I do not like it so much, though.
Remove dependency of time in all the functions . Is this always feasible ? (See more examples below)
Using namespaces  ? This looks quite good. I could mock DateTime with my own DateTime function that extends \DateTime... Use DateTime::setNow("2014-01-21 16:15:00"), and DateTime::wait("+3 minutes"). This covers pretty much what I need. What if we use the time() function though ? Or other time functions ? I still have to avoid their use in my original code. Or I would need to make sure any PHP time function I use in my code is overridden in my tests... Is there any library available that would do just this ?
I have been looking for a way to change the system time just for a thread. It seems that there is none . This is a pity: a "simple" PHP function to "Set time to 21st jan 2014 16h15 for this thread" would be a great feature for this kind of tests.
Change the system date and time for the test, using exec(). This can work out if you are not afraid to mess up with other things on the server. And you need to set back the system time after you ran your test. It can do the trick in some situations, but feels quite "hacky".
This looks like a very standard problem to me. However, I still miss a simple an generic way to deal with it. Maybe I missed something ? Please share your experience !
NOTE: Here are some other situations where we may have similar testing needs.
Any feature that works with some kind of timeout (e.g. chess game ?)
processing a queue that triggers events at a given time (in the above scenario we could keep going like this: one day before the reservation starts, I want to send a mail to the user with all the details. Test scenario...)
You want to set up a test environment with data collected in the past - you would like to see it like if it was now.