Laravel 5.7 + GraphQL（Test編）

この記事は [GraphQL Advent Calendar 2018](https://qiita.com/advent-calendar/2018/graphql) 24日目の記事です。
今回はLaravelとGraphQLでテストコードを書いてみました。

## シリーズ記事

- [LaravelとGraphQLでAPI開発](https://qiita.com/ucan-lab/items/b1796b083ac5c0bcb019)
- [Laravel 5.7 + GraphQL（Install編）](https://qiita.com/ucan-lab/items/32969d6a9b5aa99b475e)
- [Laravel 5.7 + GraphQL（ScalarType編）](https://qiita.com/ucan-lab/items/32ee8dd18974d6146456)
- [Laravel 5.7 + GraphQL（Relation編）](https://qiita.com/ucan-lab/items/4eae9e538be9a7a7c5e9)
- [Laravel 5.7 + GraphQL（Dump Server編）](https://qiita.com/ucan-lab/items/ce9abab3fddec0d2d0eb)
- [Laravel 5.7 + GraphQL（Pagination編）](https://qiita.com/ucan-lab/items/c037d4bd07840af32b94)
- [Laravel 5.7 + GraphQL（ErrorHandling編）](https://qiita.com/ucan-lab/items/fa3bf9b1e7fca1b19d0d)
- Laravel 5.7 + GraphQL（Test編）

## 今回のゴール

記事の登録ミューテーション(CreatePostMutation)のテストをします。
GraphQLのテストなので、Laravelの[HTTPテスト(FeatureTest)](https://readouble.com/laravel/5.7/ja/http-tests.html)を使います。
HTTPリクエストを作成し、GraphQLのjson結果をテストします。

## 環境

- Laravel: 5.7.15
- PHP: 7.1.19
- PHPUnit: 7.4.4

## プロダクトコード

### Postモデル

`app\Post.php` の `Eloquent\Model` を継承したモデルを用意しています。

```
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'user_id',
        'title',
        'content',
    ];

    /**
     * Get the user
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
```

### Post型の定義

`app/GraphQL/Type/PostType.php` Post型がどんなフィールドを持っているか定義します。

```
<?php

declare(strict_types = 1);

namespace App\GraphQL\Type;

use GraphQL\Type\Definition\Type;
use Folklore\GraphQL\Support\Type as BaseType;
use GraphQL;

class PostType extends BaseType
{
    protected $attributes = [
        'name' => 'PostType',
        'description' => 'post type'
    ];

    public function fields() : array
    {
        return [
            'id' => [
                'type' => Type::id(),
                'description' => 'The id of the post'
            ],
            'user_id' => [
                'type' => Type::id(),
                'description' => 'The user_id of post',
            ],
            'title' => [
                'type' => Type::string(),
                'description' => 'The title of post',
            ],
            'content' => [
                'type' => Type::string(),
                'description' => 'The content of post',
            ],
            'user' => [
                'type' => GraphQL::type('UserType'),
                'description' => 'The user of post',
            ],
        ];
    }
}
```

### CreatePostミューテーション

`app/GraphQL/Mutation/CreatePostMutation.php` 記事の登録を行うミューテーションです。

```
<?php

declare(strict_types = 1);

namespace App\GraphQL\Mutation;

use Folklore\GraphQL\Support\Mutation;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ObjectType;
use GraphQL;
use Illuminate\Support\Facades\Hash;
use App\User;
use App\Post;

/**
 * 記事を登録するミューテーション
 */
class CreatePostMutation extends Mutation
{
    /**
     * ミューテーション名の定義と概要
     *
     * @var array
     */
    protected $attributes = [
        'name' => 'CreatePost',
        'description' => 'CreatePost mutation'
    ];

    /**
     * ミューテーションが扱う型を定義
     *
     * @return ObjectType
     */
    public function type() : ObjectType
    {
        return GraphQL::type('PostType');
    }

    /**
     * ミューテーションが取り得る引数を定義
     *
     * @return array
     */
    public function args() : array
    {
        return [
            'user_id' => [
                'type' => Type::id(),
                'rules' => ['required'],
            ],
            'title' => [
                'type' => Type::string(),
                'rules' => ['required', 'string'],
            ],
            'content' => [
                'type' => Type::string(),
                'rules' => ['required', 'string'],
            ],
        ];
    }

    /**
     * ミューテーションに対する実処理
     *
     * @param array $root
     * @param array $args
     * @return Post
     */
    public function resolve($root, $args, $context, ResolveInfo $info) : Post
    {
        $user = User::findOrfail($args['user_id']);

        $post = $user->posts()->create([
            'title' => $args['title'],
            'content' => $args['content'],
        ]);

        return $post;
    }
}
```

## テストコードを書く

さて、いよいよ上記のコードのテストを書きます。

### TestCase graphql() 関数を用意する

GraphQLは `/graphql` へPOSTするオンリーワンのエンドポイントです。
エンドポイントが決まっているため、TestCaseの抽象クラスにgraphqlメソッドを用意しておくと便利です。

```
<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Foundation\Testing\TestResponse;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;

    /**
     * GraphQL POST request.
     *
     * @param  string $query
     * @param  array  $variables
     * @param  array  $data
     * @param  array  $headers
     * @return \Illuminate\Foundation\Testing\TestResponse
     */
    protected function graphql(string $query, array $variables = [], array $data = [], array $headers = []): TestResponse
    {
        $data = [
            'query' => $query,
            'variables' => json_encode($variables),
        ] + $data;

        return $this->post('/graphql', $data, $headers);
    }
}
```

### 記事の登録するテスト

`tests/Feature/ExampleTest.php`

```
<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\User;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * @test
     * @return void
     */
    public function 記事を登録するテスト()
    {
        $query = file_get_contents(__DIR__ . '/CreatePostMutation.gql');

        $user = factory(User::class)->create();

        $variables = [
            'user_id' => $user->id,
            'title' => 'テストタイトル',
            'content' => 'テスト内容',
        ];

        $json = [
            'data' => [
                'CreatePost' => $variables
            ],
        ];

        $response = $this->graphql($query, $variables);
        $response->assertStatus(200)->assertJson($json);
    }
}
```

`tests/Feature/CreatePostMutation.gql`

```
mutation (
  $user_id: ID!
  $title: String!
  $content: String!
) {
  CreatePost (
    user_id: $user_id
    title: $title
    content: $content
  ) {
    user_id
    title
    content
  }
}
```

### テスト実行！

```
$ ./vendor/bin/phpunit
PHPUnit 7.4.0 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 940 ms, Memory: 22.00MB

OK (2 tests, 3 assertions)
```

OKが返ってきたらOKです。

### クエリーファイルの配置場所

クエリーファイルの配置場所についてはわかりやすいようあえてテストファイルと同じ場所にしましたが、
クライアント側が参考にできるように、クエリーだけをまとめたディレクトリを用意すると良いかなと思います。