Christian Mayne
Christian Mayne

Reputation: 1749

Writing a test for a method that references time()

I'm just starting out with Unit testing using PHPUnit and am playing with some simple methods to get a feel for it. One example is immediately puzzling:

function setDelay($seconds)
{
    if($seconds == 0)
    {
        $this->delay = 0;
    }
    else
    {
        $this->delay = time() + $seconds;
    }
}

How would I determine the correct expected result when time() is unknown outside of the method? :

public function testSetDelayNonZero() {
  $expected = [??];
  $this->class->setDelay(100);
  $actual = $this->class->delay;
  $this->assertEquals($expected, $actual);
}

Upvotes: 2

Views: 70

Answers (2)

woru
woru

Reputation: 1420

Extracting time dependent code to a separate class is the best option.

However, sometimes it seems a bit overkill. In other languages it possible to set time globally e.g. DateTimeUtils.setCurrentMillisFixed in java or delorean in ruby.

In php you would have to use a substitute of DateTime e.g. Clock from ouzo goodies

Then in you code use:

$time = Clock::now();

In your test:

//given
Clock::freeze('2011-01-02 12:34');

//when
$result = YourCode::doSomethingWithTimeReturnedByClockNow();

//then
$this->assertEquals('2011-01-02', $result);

Upvotes: 2

Steven Scott
Steven Scott

Reputation: 11250

Basically you need to test the functionality as you would expect, with some small challenges based on your sample. The first, and easy test is when you send 0 seconds to the code. This test may be as easy as the following:

public function test_setDelay()
{
    this->assertEquals(0, setDelay(0));
}

To test that the time moved forward properly, you could mock your object so the delay always returns a set time, plus the seconds, or use dependency injection to pass the time into the function/class to have it, so you can set the object, then make the call to ensure your returned time matches what is expected.

public function test_setDelay()
{
    this->assertEquals(0, setDelay(0));

    $setDelayMock = $this->getMock('DelayClass');
    $setDelayMock->method('setDelay')
        ->with($this->equalTo(5))
        ->will($this->returnValue('10:00:05'));
    $this->assertEquals('10:00:05', $setDelayMock->setDelay(5))
}

This does not actually test the setDelay, but will help test what you do after it. Dependency Injection is likely what you are looking for to send the time to the class, and if it is not present, then use the time() function. This way, you use time() in the code about to call the setDelay, and pass it to the class, or you allow the class to create it at runtime if it is missing. For you test though, you always set the time to a known value, not time().

$currentTime = DateTime::createfromformat('hms', '100000');    

Upvotes: 1

Related Questions