Most of the answers I have seen on StackOverflow are without using the DateTime
object, and are instead using the date()
function. This makes them very dirty solutions (overriding date()
, mocking a protected function of the subject under test, etc).
Is there a way to mock DateTime
, effectively mocking the current date/time?
As an example, here is the code I'd like to test:
public function __construct(UserInterface $user, EntityManager $manager)
{
$this->user = $user;
$this->manager = $manager;
}
public function create(Tunnel $tunnel, $chain, $response)
{
$history = new CommandHistory();
$history->setTunnel($tunnel)
->setCommand($chain)
->setResponse($response)
->setUser($this->user)
;
$this->manager->persist($history);
$this->manager->flush();
}
Here is where I set the date and time in my CommandHistory
class:
class CommandHistory
{
// Property definitions...
public function __construct()
{
$this->time = new \DateTime();
}
}
And here is my unit test:
public function testCreate()
{
$user = new User();
$manager = $this->mockManagerWithUser($user);
$tunnel = $this->tunnel;
$chain = 'Commands`Chain';
$response = 'This is the response!';
$creator = new CommandHistoryCreator($user, $manager);
$creator->create($tunnel, $chain, $response);
}
protected function mockManagerWithUser(UserInterface $user)
{
$manager = \Mockery::mock('Doctrine\ORM\EntityManager');
$manager->shouldReceive('persist')->once()->with(\Mockery::on(function(CommandHistory $argument) use ($user) {
return
$argument->getCommand() === 'Commands`Chain'
&& $argument->getResponse() === 'This is the response!'
&& $argument->getTunnel() === $this->tunnel
&& $argument->getUser() === $user
;
}));
$manager->shouldReceive('flush')->once()->withNoArgs();
return $manager;
}
As you can see, I've created a rather long-winded closure only to exclude the comparison of the field that contains the current time, and I feel like this is hurting the readability of my test.
Also, to preserve ease of use for people who are using this class, I don't want to have to make them pass in the current time to the create()
function. I believe adding strange behavior to my classes only to make them testable means I'm doing something wrong.
So the standard approach to solving this relies on accepting that in your current implementation you have a static, implicit, undeclared dependency on an object which provides the current time (wrapped in a the new instance of the DateTime object). If you did this with your own code (rather than a class from the framework/language) you would not be able to test easily either.
The solution is to stop using the implicit undeclared dependency and declare your implicit dependency explictly. I would do this by creating a DateTimeProvider
(or DateTimeFactory
) interface which has a method GetCurrentDateTime
. Pass this into your constructor for your CommandHistoryCreator
and pass it into the CommandHistory
constructor. The CommandHistory
will then ask the provider to get the current date time object rather than creating a new one itself, and can carry on as it is.
This will allow you to provider a mock DateTime
in your tests and check that the CommandHistory
is persisted with the correct DateTime
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