Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I test a function that uses DateTime to get the current time?

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.

like image 379
Parham Doustdar Avatar asked May 09 '15 06:05

Parham Doustdar


1 Answers

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

like image 107
Sam Holder Avatar answered Sep 25 '22 17:09

Sam Holder