Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
211
Help us understand the problem. What is going on with this article?
@Fendo181

はじめてのLaravelアプリケーションガイド

More than 1 year has passed since last update.

はじめに

この記事ははじめてのLaravelアプリケーションを構築する為のStep by Step Guideを元に、改良したチュートリアル記事です。一部内容が被る部分はありますがLaravelを初めて触る人向けに少しだけ説明を加えています。

今回ここで作成するwebアプリケーションは以下の機能を持ったリンク投稿アプリになります。

  • 1.リンクの簡単なリストを表示します。
  • 2.新しいリンクを提出できるフォームを作成します。
  • 3.フォームを検証する
  • 4.検証が通ったらデータをデータベースに保存します。

このような簡単なwebアプリケーションをこのチュートリアルでは1つ1つ機能を紹介しながら説明していきます。しかし私がこのチュートリアルで一番に掲げる目的は、今まさにフレームワークを勉強し始めた人たちのためのガイドを作ることです。従って、Laravelの高度な機能や説明に関してはこのチュートリアルでは詳細に紹介しません。あくまでも、このチュートリアルはLaravelを始めるきっかけの1つとして作りました。

環境構築

まずはLaravelをローカル環境にインスールする作る作業からはじめます。
プロジェクトをすぐに始める為にLaravelではVagrantのboxで最初から自動で必要なファイルを用意してくれているLaravel Homesteadや、Mac向けに用意されたValetが用意されています。

また上記の方法以外にも、Dockerを使ったLaradockという手段もあります。

ここではそれぞれの方法での詳細なインスール手順は紹介しませんが、もし不安でしたら以下に手順を書いた記事のリンクを紹介するので迷ったらここを覗いて見てください。

今回のチュートリアルでは私の開発環境はLaravel Homesteadを選択しての開発を進めていきます。

プロジェクトを始める。

今回は新しくSitesディレクトリを作ってそこでLaravelのプロジェクトを新しく始めようと思います。

ターミナルを開き、Sitesディレクトリに切り替えます。

mkdir Sites
cd ~/Sites

次に、Laravelのインストーラをインストールします。

composer global require "laravel/installer"

(※既にLaravelHomSteadをインスールしているのであればここは既にインスール済みなので飛ばして大丈夫です。)

次のコマンドを実行してプロジェクトを作成します。

laravel new links

これで"links"とい名前の新しいディレクトリが作成され、laravelのプロジェクトがインスールされました。ブラウザに

localhost:8000

を入力して以下のwelcomeページが表示される事を確認して下さい。

laravel.png

MVCパターン

リンクリストを作成する前に私は、ここで1つMVCに関する簡単な講義を始めようと思います。何故、いきなりMVCについて説明をするかというと、今回のチュートリアルで作るLinkアプリケーション自体が最終的にMVCパターンの沿って実装されるからです。従って最初にこの概念が無い人にとっては、理解が難しくなってしまうので、ここでは軽くMVCパターンについて説明します。

image.png
[参考:初めてのLARAVEL 5.1 : (14) MVC]

MVCパターンの「MVC」はModel・View・Controllerの頭文字からきています。Model・View・Controller、それぞれに役割を分担させる事でプログラム全体が見通し良くなり再利用製の高い設計を実現するデザインパターンの1つです。

各役割は以下の通りです。

Controller

  • リクエスト処理、ビューとモデルの制御。
  • ユーザーからの入力を受け取る

View

  • クライアントに対してHTMLを出力する

Model

  • データ構造とそれを操作する(処理、検証、保存など)
  • データ層へのアクセス

今回新しく作成したLaravelのプロジェクトを見ると、以下のようなディレクトリ構造になっています。

.
├── app
│   ├── Console
│   ├── Exceptions
│   ├── Http
│   │   ├── Controllers # Controllerディレクトリ
│   │   ├── Kernel.php
│   │   ├── Middleware
│   │   └── Requests
│   ├── Link.php
│   ├── Providers
│   └── User.php #Model
.
.
.
├── resources
│   ├── assets
│   ├── lang
│   └── views #View
.
.
.

Laravelでは新しく作成するControllerはapp/controllerディレクトリ直下に生成されます。Viewファイルはresources/views直下に配置します。

では「Modelは?」と言うと、ここがLaravelの特徴でもあるのですが、Laravelはmodelsに相当するディレクトリがないです。理由は公式のドキュメンに詳細に記載されています。

なぜmodelsディレクトリがないのか?

Laravelを学習し始めるとき、多くの開発者はmodelsディレクトリが存在しないことに戸惑います。しかし、意図的にこのディレクトリを用意していません。多くの別々の人達にとって、その意味合いは様々なため、"models"という言葉の定義は曖昧であることに私達は気づきました。ある開発者たちはすべてのビジネスロジックを総称してアプリケーションの「モデル」と呼び、一方で別の人達はリレーショナルデータベースに関連するクラスを「モデル」として参照しています。

理由としてドキュメンにも説明されていますが、Modelとは開発者によって意味が異なってきています。単純にリレーショナルデータベースに関連するクラスを「Model」という人もいれば、解決するべき問題領域の事を指して「Model」と認識する人もいて、もはやModelという言葉は共通認識ではなくなっているのです。

したがってLarave5からModelを配置するディレクトリがなくなり、app直下にデフォルトで作られるようになりました。

詳細な説明はこちらでもまとめていますので、よろしければご覧ください。

Laravelにmodelsディレクトリがない理由 - Fendo181

Linkモデルを作成する。

DBを操作する為のLinkモデルが必要になります。以下のコマンドでLinkモデルが作られます。

php artisan make:model Link

テストデータの作成

ここではmigrationという機能を使って、直接DBにアクセスしてSQLを叩かずに、LinkListで扱うテーブルを作成します。マイグレーションファイルを作成することが最初のステップとなり、Laravel Artisanコマンドラインツールがその作成に役立ちます。

php artisan make:migration create_links_table --create=links

オプションで--create=linksをつける事で、新しくlinksテーブルを生成する事が可能です。

ファイルはdatabase / migrations / {{datetime}} _ create_links_table.phpにあります。
ファイルを開いてメソッドの中に新しいカラムを追加します。

Schema::create('links', function (Blueprint $table) {
      $table->increments('id');
      $table->string('title');
      $table->string('url')->unique();
      $table->text('description');
      $table->timestamps();
});

ファイルを保存して、マイグレーションを実行しますが、その前にMySQLに.envの設定に応じたDBとtable、そして権限付きのユーザを作成します。

create database DB_NAME;
grant all on DB_NAME.* to 'USER_NAME'@HOST_NAME identified by 'PASSWORD';

用意ができたら。マイグレーションを実行します。

php artisan migrate

これでDBに新しくLinkList用のlinksテーブルが作成されました。

tableが出来たので次はテストデータを入力してみましょう。

と言っても、まだ入力のフォーム画面も用意されてないのにどうするのか?というと、Laravelはこれを助ける2つの機能を提供します。 Seeder(初期値)とModelFactoryです。

ここでは2つの機能を使う事でテストデータを作成するModelFactoryを定義し、SeederからModelFactoryを利用することでテストデータを作成する方法を紹介します。

以下のコマンドを実行してLinkモデルを作ると同時に、LinkFactoryも一緒に生成しましょう。
Factoryでテストデータを生成する際には関連するモデルが必要な為です。

php artisan make:model --factory Link

LinkFactory.phpを開き、カスタマイズしていきます。
LinkFactory.phpはdatabase/factories/にあります。

<?php

use Faker\Generator as Faker;

/* @var Illuminate\Database\Eloquent\Factory $factory */

$factory->define(App\Link::class, function (Faker $faker) {
    return [
        'title' => substr($faker->sentence(2), 0, -1),
        'url' => $faker->url,
        'description' => $faker->paragraph,
    ];
});

title部分でテストデータを生成する際に、substrメソッドを使っているのは、テストデータで生成される文字列の文末のピリオドを削除する為です。
次に、テストデータをテーブルに簡単に追加できるように LinksTableSeederを作成します。

php artisan make:seeder LinksTableSeeder

新しく生成したseederファイルは/database/seeds/に生成されます。
作成したばかりのLinksTableSeeder.phpファイルを開き、runメソッドに上記で作成したリンクモデルファクトリを使用して、10人分のデータを用意するように記述します。

public function run()
{
    factory(App\Link::class, 10)->create();
}

DatabaseSeeder.phpを開き、runメソッドに追加します。

public function run()
{
    $this->call(LinksTableSeeder::class);
}

これでテストデータを10人分用意する事ができました。
以下のコマンドでマイグレーションとシードを実行して、テーブルにテストデータを追加します。

php artisan migrate --seed

またmigrate:freshを使う事で、一旦tableにあるデータをリセットして、seederで初期値データが入っている状態にする事もできます。

php artisan migrate:fresh --seed

データベース:マイグレーション 5.6 Laravel 全テーブル削除後のマイグレーション

ルーティングとビュー

リンクのリストを表示するビューを構築します。最初にroutes/web.phpファイルを開き、以下のルートがあることを確認して下さい。

Route::get('/', function () {
    return view('welcome');
});

最初のlocalhost:8000にアクセスした際の事を思い出して下さい。

ここでの処理は

①GETで/(root)にアクセスした際に
②viewヘルパ関数を使って resources/views/ にある welcome.blade.php を呼び出しています。

従って最初のwelcomeページの正体はwelcome.blade.htmlだとここで気付きます。

ここにリンクリストを取得するためのコードを追加してみましょう。

Route::get('/', function () {
    $links = \App\Link::all();
    return view('welcome', ['links' => $links]);
});

ここでは上から

①GETで/にアクセスした時に 
②Linksモデル(Eloquent)のallメソッドを使って、取得した全てのデータを$linksに代入する。
③view()を使って第一引数にテンプレートのキー名(welcome.balede.html)を指定して、第二引数で$linksのデータをlinksとして渡す。

と処理をしています。
次に、welcome.blade.php ファイルを編集し、単純なforeachを追加してすべてのリンクを表示します。

@foreach ($links as $link)
  <li>{{ $link->title }}</li>
@endforeach

最終的にwelcome.blade.phpは以下のようになります。

welcome.blade.php
<body>
    <div class="flex-center position-ref full-height">
        @if (Route::has('login'))
            <div class="top-right links">
                @auth
                    <a href="{{ url('/home') }}">Home</a>
                @else
                    <a href="{{ route('login') }}">Login</a>
                    <a href="{{ route('register') }}">Register</a>
                @endauth
            </div>
        @endif

        <div class="content">
            <div class="title m-b-md">
                Laravel
            </div>

            <div class="links">
                @foreach ($links as $link)
                    <a href="{{ $link->url }}">{{ $link->title }}</a>
                @endforeach
            </div>
        </div>
    </div>
</body>

ブラウザを更新すると追加されたすべてのリンクのリストが表示され、先程ModelFactoryとSeedsで生成したテストデータが表示されるのが確認できます。

リンク.png

これでLaravelを使った最初のアプリケーションの作成は終わりました。

投稿フォーム

次の実装する大きな機能は、フォームからリンクを追加できるようにする事です。
これには、タイトル、URL、説明の3つのフィールドが必要です。

リンクリストに必要な要件を洗い出します。

  • タイトル
  • URL
  • 説明

次に今回作成するフォームの簡単な図です。

laravel_mock.png

まず、routes/web.phpに新しい投稿フォーム用のルーティングを作成します。

Route::get('/submit', function () {
    return view('submit');
});

ここでの処理も先程同様に

①GETで /submit にアクセスした時に 
②viewヘルパ関数を使って resources/views/ にある submit.blade.php を呼び出しています。

ここからは実際にテンプレート元になるdefault.blade.phpと、投稿フォーム用のsubmit.blade.phpを作ります。

なぜ2つのファイルを用意するのか?というと、LaravelのViewで使われるBladeテンプレートは共通部分をテンプレート化して、そのテンプレートを利用して継承する事ができます。
詳しくは次の章で説明します。

Bladeテンプレートの概要説明

submit.blade.phpを作成する前にここで、簡単なBladeテンプレートの利点である継承とセクションについて紹介します。実際に例をみてみましょう。

app.blade.php
<!-- resources/views/layouts/app.blade.phpとして保存 -->

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
     <!-- Bootstrap4 CDN -->
    <title>@yield('title')</title>
</head>
<body>
    {{-- コンテンツの表示 --}}
    <div class="container">
        @yield('content')
    </div>
</body>

app.blade.phpは親テンプレートになります。
ここで大事なので@yield('title')@yield('content')です。これはセクション機能と呼ばれます。この親テンプレート(default.blade.php)を継承する事で、継承先の子テンプレートで@sectionディレクティブを定義する事により、指定した内容を表示する事ができます。

例えばこんな感じにapp.blade.phpを継承した子テンプレート(default.blade.php)を用意するとします。

default.blade.php
<!-- resources/views/default.blade.phpとして保存 -->

<!-- 親元のテンプレートを継承する。 -->
@extends('layouts.app')


@section('title', 'Laravelチュートリアル')

@section('content')
  <p>ここが本文のコンテンツになります。</p>
@endsection

この用に定義する事で、子テンプレートは親テンプレートを継承しているので読み込まれる際はコードが置き換えられて表示されるようになります。

<html>
    <head>
        <title>Laravelチュートリアル</title>
    </head>
    <body>
        <div class="container">
           <p>ここが本文のコンテンツになります。</p>
        </div>
    </body>
</html>

上記のように、子テンプレートは、親テンプレートを継承する事で少ない記述で済み、変更箇所のみテンプレートに書けばいいので、シンプルで分かりやすく、保守しやすいViewファイルが作られるようになっています。

投稿フォームを作成する

先程作成したdefault.blade.phpを親テンプレートとして使います。
親テンプレートを継承する際は@extendsセクションを使ってください。

resources/views/submit.blade.phpに以下のコードを追加して作成して下さい。

@extends('default')

@section('content')
    <div class="container">
        <div class="row">
            <h1>Submit a link</h1>
            <form action="/submit" method="post">
                {!! csrf_field() !!}

                <div class="form-group">
                    <label for="title">Title</label>
                    <input type="text" class="form-control" id="title" name="title" placeholder="Title">
                </div>

                <div class="form-group">
                <label for="url">Url</label>
                    <input type="text" class="form-control" id="url" name="url" placeholder="URL">
                </div>

                <div class="form-group">
                    <label for="description">Description</label>
                    <textarea class="form-control" id="description" name="description" placeholder="description"></textarea>
                </div>

                <button type="submit" class="btn btn-default">Submit</button>
            </form>
        </div>
    </div>
@endsection

localhost:8000/submitにアクセスして以下のフォーム画面が表示される事を確認して下さい。

リンク.png

投稿フォームのバリデーションとルーティング

ここでは一気にルーティングからフォームの検証とモデル操作を1つのファイルで一気に処理しています。
再度`routes/web.phpを開いて、以下の処理を追加させます。

use Illuminate\Http\Request;

Route::post('/submit', function (Request $request) {
    $data = $request->validate([
        'title' => 'required | max:255',
        'url'  => 'required | url | max:255',
        'description' => 'required | max:255',
    ]);

    $link = new App\Link($data);
    $link->save();

    return redirect('/');
});

上から処理をみていきましょう。

①POSTで/submitにアクセスする。
②validateメソッドを使ってバリデーションを行う
エラーが発生した場合、、セッションにエラーメッセージをフラッシュデータとして保存します。
③バリデーションの検証が通ったらLinkモデルを生成してフォームに投稿されたデータをDBに保存する。
④その後に/(root)にリダイレクトさせる。

②のvalidateメソッドはLaravel 5.5で追加されたメソッドです。
バリデーションルールに成功するとコードは通常通り続けて実行されます。
逆にバリデーションに失敗すると例外が投げられ、ユーザーに対し自動的に適切なエラーレスポンスが返されようになっています。

バリデーション 5.6 Laravel

上の処理を意識してもう一度コードを見てみましょう。

use Illuminate\Http\Request;

// ①POSTで/submitにアクセスする。
Route::post('/submit', function (Request $request) {
    // ②validateメソッドを使ってバリデーションを行う
    $data = $request->validate([
        'title' => 'required | max:255',
        'url'  => 'required | url | max:255',
        'description' => 'required | max:255',
    ]);

    // ③ Linkモデルを生成
    $link = new App\Link($data);
    $link->save();

    // リダイレクトする
    return redirect('/');
});

これで投稿フォームのバリデーションの準備はできたので、次に送られてきた値をデータベースに保存する為、Linkモデルを調整していきます。

Laravelではリクエストで送られてきた値を同時に保存するmass-assignedを防ぐために、DBに保存する値をfillableプロパティで制限する事ができます。
以下のようにfillablを使用して保存可能なフィールドを定義します。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Link extends Model
{
    protected $fillable = [
        'title',
        'url',
        'description'
    ];
}

これで、投稿フォームから送られたリンクデータがDBに保存されるようになります。

リファクタリング

ここから先はリファクタリングの話になります。
私がこれを見たときは真っ先になぜ複雑なバリデーションの処理をroutes/web.phpに責務を追わせているのか?と考えました。というのは、web.phpはあくまでもルーティング処理を任せて、ここでモデル操作や、バリデーションをするのは、適切ではないと感じたのです。

従ってここでは上のMVCパターンのようContrller側でモデルを呼び出しビジネスロジックを担保し、Requestでバリデーションを行うようにリファクタリングをしてみます。

バリデーション:LinkRequest

バリデーションを記述するRequestファイルを生成します。

php artisan make:request LinkRequest

app/Http/Requests/LinkRequest.phpが新し生成されました。
rulesメソッドに先程ルーティンで書いたバリデーションをruleメソッドに記述します。

<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class LinkRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|max:255',
            'url' => 'required|max:255|',
            'description' => 'required|max:255',
        ];
    }
}

気づいたと思いますが検証が失敗した場合の$validator->fails()の処理が記述されてないです。
Laravelはバリデーションで通らなかった場合、自動的にユーザを直前にアクセスしたページヘリダイレクトします。
付け加えて、バリデーションエラーは全て自動的にフラッシュデータとしてセッションへ保存されます。従って明示的に宣言しなくてみいつでもビューの中で$errors変数が使えるようになっています。

モデル操作:LinkController

次に実際にフォームからのデータを受け取ってモデル操作を行うLinkControllerを作成します。

php artisan make:controller LinkController

LinkControllerが生成されたら以下のよう処置を追加します。

<?php
namespace App\Http\Controllers;
use App\Link;
use App\Http\Requests\LinkRequest;

class LinkController extends Controller
{
    public function submit(LinkRequest $request){
        $link = new Link();
        $link->title = $request->title;
        $link->url = $request->url;
        $link->description = $request->description;
        $link->save();
        return redirect('/');
    }
}

useを使ってApp\Http\Requests\LinkRequest;を名前空間として呼び出している事に注意して下さい。
ここでは新しくsubmitメソッドを作って、先程作成したLinkRequestをタイプヒントで指定する事でバリデーションの検証を行う事ができます。検証が通った後はモデル操作を行います。

ルーティング:web.php

先程のルーティングで処理していた役割をそれぞれ、LinkControllerLinkRequestで関心の分離をした事でルーティングはこの用に綺麗になりました。

use Illuminate\Http\Request;
Route::post('/submit','LinkController@submit');
①postで/submitにアクセスした際に、LinkControllerのsubmitアクションを呼び出す。

これで以前よりも随分簡潔になりました
今回のケースの場合はアクションがsubmitのみなので、リファクタリングする前ではルーティングで全ての処理をまとめる考えも間違ってはないのですが、今後リンクを消したり、編集したり等のCRUD操作を導入するとなるとルーティングファイルが肥大化して可読性が下がってしまいます。

なので今回のように、ControllerとRequestで処理を分離させる手法を見せましたが、Laravelを知れば知る程このように関心の分離が容易に実現できる事に魅力を感じてくると思います。  
是非興味があれば、Laravelの醍醐味であるDIコンテナについて調べると良いでしょう。

投稿フォームのテスト

投稿フォームが完成しバリデーションも追加したので、ここで投稿フォームでの処理が正しく動作する事を担保するFeatureテストを追加しましょう。 Laravelでは標準でphpunitが入っているので、普段PHPのアプリケーションで書いてる時と同じ要領でテストが実行できます。

最初にデフォルトで存在するテストファイルを削除します。

rm tests/Feature/ExampleTest.php

次に以下のコマンドを実行して、新しく投稿フォーム用のテストファイルを生成します。

php artisan make:test SubmitLinksTes

生成されたテストファイルに予め今回テストするテストケースを追加します。

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class SubmitLinkTest extends TestCase
{
    /** @test */
    function guest_can_submit_a_new_link() {}

    /** @test */
    function link_is_not_created_if_validation_fails() {}

    /** @test */
    function link_is_not_created_with_an_invalid_url() {}

    /** @test */
    function max_length_fails_when_too_long() {}

    /** @test */
    function max_length_succeeds_when_under_max() {}
}

これらは、これから記述していくテストコードの概要を示してます。

  • 1.有効なリンクがデータベースに保存されることを確認する
  • 2.バリデーションに失敗すると、リンクはデータベースに保存されない
  • 3.有効でないリンクはバリデーションで失敗する
  • 4.フィールドに入力された文字がmax:255よりも長い場合は失敗する
  • 5.フィールドに入力された文字がmax:255より少ない場合は成功する

では1つ1つのテストケースを通していきましょう。

有効なリンクは保存できる場合のテスト

最初に行うテストでは有効なリンクがデータベースに保存されることを確認するテストを実行します

<?php

namespace Tests\Feature;

use Illuminate\Validation\ValidationException;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class SubmitLinksTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    function guest_can_submit_a_new_link()
    {
        $response = $this->post('/submit', [
            'title' => 'Example Title',
            'url' => 'http://example.com',
            'description' => 'Example description.',
        ]);

        $this->assertDatabaseHas('links', [
            'title' => 'Example Title'
        ]);

        $response
            ->assertStatus(302)
            ->assertHeader('Location', url('/'));

        $this
            ->get('/')
            ->assertSee('Example Title');
    }
}

RefreshDatabaseトレイトは、テスト後にデータベースをリセットできる為、テストデータに影響を及ぼさないようにする事が可能です。 ここでは最初のテストで$this->postを使って、有効なデータを/submitでpostした際に、レスポンスで帰ってくるオブジェクト($response)を使って期待どおりに処理になっているかを確認しています。

assertDatabaseHasは指定したカラムに該当するデータが存在しているかを確認しています。

次に、assertStatusを使って返ってくるレスポンスのでステータスコードが正しいかを確認しています。302なので、正常にリダイレクトされている事を期待しています。
最後に、assertSeeを使ってホームページをリクエストして、その後ホームページにリンクのタイトルが表示されていることを確認しています。

この状態でまずはテストを実行してみます。

$./vendor/bin/phpunit ./tests/Feature/SubmitLinkTest.php

.                                1 /1 (100%)

Time: 146 ms, Memory: 16.00MB

OK (1 test, 5 assertions)

無事に通りました。
こんな感じでテストを追加して実行していきます。

バリデーション失敗時のテスト

ユーザーが一般的に不正なデータを送信すると、バリデーション時に例外が発生するのでこれを使ってバリデーション時の機能が動作しているかを確認する事ができます。

/** @test */
function link_is_not_created_if_validation_fails()
{
    $response = $this->post('/submit');

    $response->assertSessionHasErrors(['title', 'url', 'description']);
}

assertSessionHasErrorsを使用して各フィールドに対してバリデーションエラーがあることを確認します。
ここで行っているテストはルーティングには空のデータが投稿された場合の為、各フィールドに対して必須項目のバリデーションで落ちる事を確認できます。

URLバリデーション時のテスト

ここのテストケースでは有効なURLのみがバリデーションを通過して、アプリケーション側で無効なデータが表示されない事を期待します。

/** @test */
function link_is_not_created_with_an_invalid_url()
{
    $this->withoutExceptionHandling();

    $cases = ['//invalid-url.com', '/invalid-url', 'foo.com'];

    foreach ($cases as $case) {
        try {
            $response = $this->post('/submit', [
                'title' => 'Example Title',
                'url' => $case,
                'description' => 'Example description',
            ]);
        } catch (ValidationException $e) {
            $this->assertEquals(
                'The url format is invalid.',
                $e->validator->errors()->first('url')
            );
            continue;
        }

        $this->fail("The URL $case passed validation when it should have failed.");
    }
}

withoutExceptionHandling()に注目してください。
これを追加する事で、Laravle側でHTTPレスポンスを生成する際の例外処理が原因で、それ以降のテストが実行できないのを防ぐ事ができます。

ここではforeachを使って使ってさまざまなケースを試しています。
誤ったテキストがpostされると、そのたびにValidationExcepiton例外が発生して手動で落ちるようになっています。

エラーが発生した場合は、assertEqualsを使って表示されるエラーメッセージが期待しているものと一致しているかを確認しています。

MaxLengthバリデーション時のテスト

次に max:255のバリデーションルールに関するテストケースを用意して、期待通りに動作するか確認します。
フィールドが長さ 256文字の最大長の場合はバリデーションに失敗し、フィールドが255文字以下の場合はバリデーションが通る事を確認します。
最小と最大のバリデーションルールのしきい値をテストして、アプリケーションが設定した最小値と最大の境界を担保しているかどうかをテストケースで実装していきます。

/** @test */
function max_length_fails_when_too_long()
{
    $this->withoutExceptionHandling();

    $title = str_repeat('a', 256);
    $description = str_repeat('a', 256);
    $url = 'http://';
    $url .= str_repeat('a', 256 - strlen($url));

    try {
        $this->post('/submit', compact('title', 'url', 'description'));
    } catch(ValidationException $e) {
        $this->assertEquals(
            'The title may not be greater than 255 characters.',
            $e->validator->errors()->first('title')
        );

        $this->assertEquals(
            'The url may not be greater than 255 characters.',
            $e->validator->errors()->first('url')
        );

        $this->assertEquals(
            'The description may not be greater than 255 characters.',
            $e->validator->errors()->first('description')
        );xw
        return;
    }

    $this->fail('Max length should trigger a ValidationException');
}

各フィールドにmax lengthのバリデーション時のエラーメッセージがあることを確認するために、各フィールドに対してassertEqualsを使って確認しています。
最後に例外処理が発生した事を$this->fail()を使って確認します。

次にmax lengthよりも少ない場合のテストケースを作成します。

/** @test */
function max_length_succeeds_when_under_max()
{
    $url = 'http://';
    $url .= str_repeat('a', 255 - strlen($url));

    $data = [
        'title' => str_repeat('a', 255),
        'url' => $url,
        'description' => str_repeat('a', 255),
    ];

    $this->post('/submit', $data);

    $this->assertDatabaseHas('links', $data);
}

このテストケースではmax:255バリデーションで通る長さのデータを作成して、データを送信した後にデータがデータベースに格納されている事を確認します。

最後にこれらの作成したテストを実行して結果を確認します。

$./vendor/bin/phpunit ./tests/Feature/SubmitLinkTest.php
PHPUnit 7.4.5 by Sebastian Bergmannand contributors.

.....                                5 /5 (100%)

Time: 176 ms, Memory: 16.00MB

OK (5 tests, 16 assertions)

お疲れ様です。
以上で投稿フォームを担保するテストコードを追加する作業は終わりです。

おわりに

チュートリアルを完了したことを祝福します。
このガイドは最初にも述べたように、はじめてのLaravelアプリケーションを構築する為のStep by Step Guideを元に、改良したチュートリアル記事です。
この記事以外にもチュートリアルを探せばいくらでも出て来るので、是非このチュートリアルをきっかけに他のLaravelでのアプリケーション開発に興味を持って頂ければ幸いです。

211
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Fendo181
Enduです。PHPとJavaScriptを書いてご飯食べてます。
pepabo
「いるだけで成長できる環境」を標榜し、エンジニアが楽しく開発できるWebサービス企業を目指しています。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
211
Help us understand the problem. What is going on with this article?