PHPUnitでtime()をテストする

time関数とか使ってる時、どうやってテストするか書いてみます。

### テスト出来ないコード

2014年12月31日23:59:59までは割引するようなコードを書いたとします。
これ、どうやってテストしますか。
テストを実施する時間によってisDiscountの結果が変わってしまうのでテストできません。

```php
<?php

class Campaign
{
    /**
     * 割引するか
     *
     * @return boolean
     */

    public function isDiscount()
    {
        // 2014年12月31日までは割引する
        return time() < strtotime('2014-12-31 23:59:59');
    }
}
```

### 解決策1. 現在時間を取得するメソッドを作る

isDiscountの中でtime関数を使わずに、別メソッドで現在時間を取得する形にすれば、
getNowをモック化すればテストできそうです。

```php
<?php

class Campaign
{
    /**
     * 割引するか
     *
     * @return boolean
     */

    public function isDiscount()
    {
        // 2014年12月31日までは割引する
        return $this->getNow() < strtotime('2014-12-31 23:59:59');
    }

    public function getNow()
    {
        return time();
    }
}
```

getNowをモック化してあげればテストできそうです。

```php
class CampaignTest extends PHPUnit_Framework_TestCase
{
    public function testIsEnable()
    {
        $c = $this->getMock('Campaign', array('getNow'));
        $c->expects($this->at(0))
            ->method('getNow')
            ->will($this->returnValue(strtotime('2014-12-31 23:59:59')));

        $c->expects($this->at(1))
            ->method('getNow')
            ->will($this->returnValue(strtotime('2015-01-01 00:00:00')));

        $this->assertTrue($c->isDiscount());
        $this->assertFalse($c->isDiscount());
    }
}
```

### 解決策2. コンストラクタにタイムスタンプを渡す

```php
class Campaign
{
    public $nowTimeStamp = null;

    public function __construct($nowTimeStamp)
    {
        $this->nowTimeStamp = (empty($nowTimeStamp)) ? time() : $nowTimeStamp;
    }

    /**
     * 割引するか
     *
     * @return boolean
     */
    public function isDiscount()
    {
        // 2014年12月31日までは割引する
        return $this->nowTimeStamp <= strtotime('2014-12-31 23:59:59');
    }
}
```

これならコンストラクタに渡す値を操作すればテストできます。

```php
class CampaignTest extends PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider providerIsDiscount
     */
    public function testIsDiscount($timeStamp, $expected)
    {
        $c = new Campaign($timeStamp);

        $this->assertEquals($expected, $c->isDiscount());
    }

    public function providerIsDiscount()
    {
        return array(
            array(
                strtotime('2014-12-31 23:59:59'),
                true
            ),
            array(
                strtotime('2015-01-01 00:00:00'),
                false
            )
        );
    }
}
```

### 解決策3. セッターでタイムスタンプを渡す

コンストラクタで渡すのが嫌な場合、セッターで渡すという手もあります。

```php
class Campaign
{
    public $nowTimeStamp = null;

    public function setNowTimeStamp($nowTimeStamp)
    {
        $this->nowTimeStamp = $nowTimeStamp;
    }

    /**
     * 割引するか
     *
     * @return boolean
     */
    public function isDiscount()
    {
        // 2014年12月31日までは割引する
        return $this->nowTimeStamp <= strtotime('2014-12-31 23:59:59');
    }
}
```

* テスト

```php
class CampaignTest extends PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider providerIsDiscount
     */
    public function testIsDiscount($timeStamp, $expected)
    {
        $c = new Campaign();
        $c->setNowTimeStamp($timeStamp);

        $this->assertEquals($expected, $c->isDiscount());
    }

    public function providerIsDiscount()
    {
        return array(
            array(
                strtotime('2014-12-31 23:59:59'),
                true
            ),
            array(
                strtotime('2015-01-01 00:00:00'),
                false
            )
        );
    }
}
```

### 終わりに

今回3つの方法をご紹介しましたが、他にも方法はあると思います。
もし他にも方法をご存知の方がいらっしゃいましたらコメントいただけると嬉しいです。