Laravel API Resourceのテスト

# 概要

調べ方が悪いのかもしれないけど、APIResourceのテストを書いた記事が見つけられなかった。
Featureテストを使えばリクエストからレスポンスまでをテスト出来るので、あまり需要はないかもしれないけど備忘録として残しておく。

## 環境

- Laravel10
- PHP8.1
- MySQL8

## Resource に Model を渡すケース

ControllerからユーザーモデルをResourceクラスに渡す

```php: UserShowController.php
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Resources\UserShowResponseResource;

final class UserShowController extends Controller
{
    /**
     * @param int $userId
     *
     * @return UserShowResponseResource
     */
    public function __invoke(int $userId): UserShowResponseResource
    {
        // ユーザーを取得
        $user = User::where('id', $userId)->first();

        return new UserShowResponseResource($user);
    }
}
```

Resourceの実装はこんな感じ

```php: UserShowResponseResource.php
<?php

declare(strict_types=1);

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class UserShowResponseResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
     */
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at->toDateTimeString(),
            'updated_at' => $this->updated_at->toDateTimeString()
        ];
    }
}
```

実際のテストはこちら

```php: UserShowResponseResource.php
<?php

declare(strict_types=1);

namespace Tests\Unit\Http\Resources;

use App\Http\Resources\UserShowResponseResource;
use App\Models\User;
use Illuminate\Http\Request;
use Tests\TestCase;

class UserShowResponseResourceTest extends TestCase
{
    /**
     * @test
     */
    public function ユーザー詳細のレスポンスを取得できる()
    {
        $user = new User();
        $user->id = 1;
        $user->name = '田中 太郎';
        $user->email = 'user@example.com';
        $user->created_at = '2024-01-01 00:00:00';
        $user->updated_at = '2024-01-01 00:00:00';

        $expected = [
            'id' => 1,
            'name' => '田中 太郎',
            'email' => 'user@example.com',
            'created_at' => '2024-01-01 00:00:00',
            'updated_at' => '2024-01-01 00:00:00'
        ];

        $this->assertEquals($expected, (new UserShowResponseResource($user))->toArray(Request::class));
    }
}
```

:::note warn
注意
例ではプロパティを代入していますが、以下のようにモデルに値を渡すようにすると $fillable に登録していない `id` や `created_at` `updated_at` の返り値がnullになってしまうので気をつけましょう。
:::

```php
        $user = new User([
            'id' => 1,
            'name' => '田中 太郎',
            'email' => 'user@example.com',
            'created_at' => '2024-01-01 00:00:00',
            'updated_at' => '2024-01-01 00:00:00'
        ]);
```

## Resource に Collection を渡すケース

ControllerからユーザーモデルをResourceクラスに渡す

```php: UserListController.php
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Resources\UserListResponseResource;

final class UserListController extends Controller
{
    /**
     * @return UserListResponseResource
     */
    public function __invoke(): UserListResponseResource
    {
        // ユーザー一覧を取得
        $users = User::get();

        return new UserListResponseResource($users);
    }
}
```

Resourceの実装はこんな感じ

```php: UserListResponseResource.php
<?php

declare(strict_types=1);

namespace App\Http\Resources;

use App\Models\User;
use Illuminate\Http\Resources\Json\ResourceCollection;

class UserListResponseResource extends ResourceCollection
{
    /**
     * Transform the resource into an array.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
     */
    public function toArray($request): array
    {
        return $this->resource->map(fn (User $user) => [
            'id' => $user->id,
            'name' => $user->name,
            'email' => $user->email,
            'created_at' => $user->created_at->toDateTimeString(),
            'updated_at' => $user->updated_at->toDateTimeString()
        ])->all();
    }
}
```

実際のテストはこちら


```php: UserListResponseResource.php
<?php

declare(strict_types=1);

namespace Tests\Unit\Http\Resources;

use App\Http\Resources\UserListResponseResource;
use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Tests\TestCase;

class UserListResponseResourceTest extends TestCase
{
    /**
     * @test
     */
    public function ユーザー一覧のレスポンスを取得できる()
    {
        $user = new User();
        $user->id = 1;
        $user->name = '田中 太郎';
        $user->name_kana = 'たなか たろう';
        $user->email = 'user@example.com';

        $users = new Collection([$user]);

        $expected = [
            [
                'id' => 1,
                'name' => '田中 太郎',
                'name_kana' => 'たなか たろう',
                'email' => 'user@example.com'
            ],
        ];

        $this->assertEquals($expected, (new UserListResponseResource($users))->toArray(Request::class));
    }
}
```

## Model に Collection が紐づいているケース

Controllerから投稿が紐づいたユーザーモデルをResourceクラスに渡す

```php: UserShowController.php
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Resources\UserShowResponseResource;

final class UserShowController extends Controller
{
    /**
     * @param int $userId
     *
     * @return UserShowResponseResource
     */
    public function __invoke(int $userId): UserShowResponseResource
    {
        // ユーザーを取得
        $user = User::where('id', $userId)->with('posts')->first();

        return new UserShowResponseResource($user);
    }
}
```

Resourceの実装はこんな感じ

```php: UserShowResponseResource.php
<?php

declare(strict_types=1);

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;
use App\Models\Post;

class UserShowResponseResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
     */
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'posts' => $this->resource->map(fn (Post $post) => ['title' => $post->title])->all();
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at->toDateTimeString(),
            'updated_at' => $this->updated_at->toDateTimeString()
        ];
    }
}
```

実際のテストはこちら

```php: UserShowResponseResource.php
<?php

declare(strict_types=1);

namespace Tests\Unit\Http\Resources;

use App\Http\Resources\UserShowResponseResource;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Tests\TestCase;

class UserShowResponseResourceTest extends TestCase
{
    /**
     * @test
     */
    public function ユーザー詳細のレスポンスを取得できる()
    {
        $post = new Post();
        $post->title = '本タイトル';
        
        $user = new User();
        $user->id = 1;
        $user->posts = new Collection([$post]);
        $user->name = '田中 太郎';
        $user->email = 'user@example.com';
        $user->created_at = '2024-01-01 00:00:00';
        $user->updated_at = '2024-01-01 00:00:00';

        $expected = [
            'id' => 1,
            'posts' => [['title' => '本タイトル']],
            'name' => '田中 太郎',
            'email' => 'user@example.com',
            'created_at' => '2024-01-01 00:00:00',
            'updated_at' => '2024-01-01 00:00:00'
        ];

        $this->assertEquals($expected, (new UserShowResponseResource($user))->toArray(Request::class));
    }
}
```