Laravel 8 の新機能！時間テストヘルパー(タイムトラベル)を使ってみた！

## 時間テストヘルパーとは

時間テストヘルパー(タイムトラベル)はLaravel8から追加された新しい機能です。
実行する時間によって挙動が異なるロジックのテストが簡単に行えます。

現実にあったらいいなと思うロマン溢れる機能です。

## 環境

- PHP 7.4.4
- Laravel 8.7.1

https://github.com/ucan-lab/docker-laravel

## 使い方

```php
// 5分進む
$this->travel(5)->minutes();
$this->get($route)->assertSee('Created 5 mins ago');

// 1年先に進む
$this->travel(1)->year();
$this->get($route)->assertSee('Created 1 year ago');

// 指定された日時に移動する
$this->travelTo($user->trial_ends_at);
$this->get($route)->assertSee('Your free trial is expired');
```

内部的には、[CarbonのsetTestNow](https://carbon.nesbot.com/docs/#api-testing)のラッパーです。

```php
// 未来へタイムトラベル
$this->travel(5)->milliseconds();
$this->travel(5)->seconds();
$this->travel(5)->minutes();
$this->travel(5)->hours();
$this->travel(5)->days();
$this->travel(5)->weeks();
$this->travel(5)->years();

// 過去へタイムトラベル
$this->travel(-5)->hours();

// 正確な時間に移動
$this->travelTo(now()->subHours(6));

// そして時は動き出す...(現在に戻る)
$this->travelBack();
```

## 例: Twitterのような投稿日時のテストをする

Twitterのように投稿日時を「X分前」や「X時間前」という表示するプログラムがあった場合のテストを考えます。
(サンプルコードなのでルーティングにコードをそのまま書いてます😇)

```routes/web.php
<?php declare(strict_types=1);

use Illuminate\Support\Facades\Route;
use Carbon\Carbon;

Route::get('/fuzzy-time/{created?}', function (string $created = null) {
    $createdAt = new Carbon($created);
    $now = new Carbon();

    if ($diff = $now->diffInYears($createdAt)) {
        return sprintf('Created %d %s ago', $diff, $diff > 1 ? 'years' : 'year');
    }

    if ($diff = $now->diffInMonths($createdAt)) {
        return sprintf('Created %d %s ago', $diff, $diff > 1 ? 'months' : 'month');
    }

    if ($diff = $now->diffInDays($createdAt)) {
        return sprintf('Created %d %s ago', $diff, $diff > 1 ? 'days' : 'day');
    }

    if ($diff = $now->diffInHours($createdAt)) {
        return sprintf('Created %d %s ago', $diff, $diff > 1 ? 'hours' : 'hour');
    }

    if ($diff = $now->diffInMinutes($createdAt)) {
        return sprintf('Created %d %s ago', $diff, $diff > 1 ? 'minutes' : 'minute');
    }

    if ($diff = $now->diffInSeconds($createdAt)) {
        return sprintf('Created %d %s ago', $diff, $diff > 1 ? 'seconds' : 'second');
    }

    throw new Exception('Not created.');
});
```

試しに動作確認してみます。
(※ 2020/09/30 に実行してます)

```
$ curl http://127.0.0.1/fuzzy-time/20200901
Created 29 days ago
```

このように実行する時間によって挙動が異なるロジックのテストは難しいですが、タイムトラベルを使うとこんなに簡単に書けます。

```shell-session
$ php artisan make:test FuzzyTimeTest
```

```php:tests/Feature/FuzzyTimeTest.php
<?php declare(strict_types=1);

namespace Tests\Feature;

use Carbon\Carbon;
use Exception;
use Tests\TestCase;

class FuzzyTimeTest extends TestCase
{
    public function test作成日から1年後のテスト()
    {
        $created = (new Carbon())->format('Y-m-d H:i:s');
        $this->travel(1)->years();
        $this->get('/fuzzy-time/' . $created)->assertSee('Created 1 year ago');
    }

    public function test作成日から5分後のテスト()
    {
        $created = (new Carbon())->format('Y-m-d H:i:s');
        $this->travel(5)->minutes();
        $this->get('/fuzzy-time/' . $created)->assertSee('Created 5 minutes ago');
    }

    public function test作成日から10年後のテスト()
    {
        $created = (new Carbon())->format('Y-m-d H:i:s');
        $this->travel(10)->years();
        $this->get('/fuzzy-time/' . $created)->assertSee('Created 10 years ago');
    }

    public function test作成日より1分前のテスト()
    {
        $created = (new Carbon())->format('Y-m-d H:i:s');
        $this->travel(-1)->minutes();
        $this->get('/fuzzy-time/' . $created)->assertStatus(500);
    }
}
```

テストを実行します。

```
$ php artisan test

   PASS  Tests\Feature\FuzzyTimeTest
  ✓ 作成日から1年後のテスト
  ✓ 作成日から5分後のテスト
  ✓ 作成日から10年後のテスト
  ✓ 作成日より1分前のテスト

  Tests:  4 passed
  Time:   2.64s
```

テストが通ったらokです。

## 参考

- https://laravel-news.com/laravel-time-traveling
- [Laravel 8 Release !](https://qiita.com/ucan-lab/items/e329af0bd01dda179d31)
- [投稿日時をTwitterのように「X分前」「X時間前」という文字列に変換するPHP関数](https://qiita.com/wgkoro@github/items/eee4e6854535d62ca55b)