You are here
Home > ユーザー認証のテスト Laravel 5.4

ユーザー認証のテスト(3) Laravel 5.4 ログアウト

前回のログインのテストに対して、今回はログアウトのテスト。

まず、以下を見てください。

   /** @test */
    public function logout()
    {
        // ユーザーを1つ作成
        $user = factory(User::class)->create();

        // 認証済み、つまりログイン済みしたことにする
        $this->actingAs($user);

        // 認証されていることを確認
        $this->assertTrue(Auth::check());

        // ログアウトを実行
        $response = $this->post('logout');

        // 認証されていない
        $this->assertFalse(Auth::check());

        // Welcomeページにリダイレクトすることを確認
        $response->assertRedirect('/');
    }

前回と違うのは、ログインの実行$response = $this->post(..)が今回のテストにはないことです。その代わりに、actingAs()を利用して、プログラムで認証済みにしてしまいます。

actingAs()のおかげで、コードが短くなりわかりやすくなっただけでなく、その使用では以下のようにセッション値を入れたり、リダイレクトすることも可能です。認証された後の、つまりパスワード保護された中の機能のテストではいつも必要なことなので、将来重宝しそうです。

..
 $response = $this->actingAs($user)
                   ->withSession(['foo' => 'bar'])
                   ->get('/');
...

さて、テストとは関係ないですが、ログアウト後はどうして、/loginでなく/にリダイレクトされるのでしょう?また、その変更は可能なのでしょうか?

リダイレクト先は、残念ながら、以下のlaravelのパッケージの中のファイルでハードコードされています。

vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticateUsers.php

...
   /**
     * Log the user out of the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function logout(Request $request)
    {
        $this->guard()->logout();

        $request->session()->invalidate();

        return redirect('/');
    }
...

上のファイルは編集するべきではありません。しかし、ログアウト後のリダイレクト先を変更したいなら、

app/Http/Controllers/Auth/LoginController.php

に、上のlogoutのコードを追加して、リダイレクト先を編集すればよいです。親のメソッドが上書きされて変更されることになります。use Illuminate\Http\Request;の行の追加も忘れないように。以下に全コードを掲載します。

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    /**
     * Log the user out of the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function logout(Request $request)
    {
        $this->guard()->logout();

        $request->session()->invalidate();

        return redirect('/');
    }
}

最後に今回のテストのコードは以下から利用可能です。

https://github.com/lotsofbytes/larajapan/blob/l54-test/tests/Feature/LoginTest.php

ユーザー認証のテスト(2) Laravel 5.4 ログイン

前回は、

  • ログイン画面にアクセスできる
  • ログインしないで、ホームページにアクセスするとログイン画面にリダイレクトされる

のテストを作成しました。

今回は、同じくログイン画面において以下のテストを作成します。

  • 登録されているユーザーのEメールとパスワードでログインできる
  • 間違ったパスワードでログインした場合、ログイン失敗してエラーが出力される

モデルファクトリ

さて、前回と違って今回は、ログインのテストゆえにテストのデータベースにユーザーのレコードが必要となります。もちろん、手動でレコードを作成しておいてテストも可能です。しかし、テストが複雑になって様々なケースのテストが増えてくると非常に面倒です。

そこで登場するのは、モデルファクトリ。テストのために、必要なデータを必要な数だけ自動生成してDBレコードを作成してくれます。ファクトリの設定は、LaravelのEloquentのモデルを指定するのでとても簡単。以下ではUserのモデルをもとにしています。Fakerのパッケージを利用しているので各項目での詳細はそちらを参照してください。

use Faker\Generator as Faker;

/** @var \Illuminate\Database\Eloquent\Factory $factory */
$factory->define(App\User::class, function (Faker $faker) {
    static $password;

    return [
        'email'          => $faker->unique()->safeEmail, // 複数レコード作成時には重複しないEメールを生成
        'password'       => $password ?: $password = bcrypt('test1234'), // 複数レコード作成時には同じパスワードを生成
        'name'           => $faker->name,
        'remember_token' => str_random(100), //ランダム値
    ];
});

上の設定では、複数のレコード作成時には、emailは重複しないように、パスワードはstatic変数を利用してどのレコードも同じパスワードを作成するようなっています。bcryptは、LaravelのヘルパーでHash::makeと同じです。

日本語のデータを生成したいなら、config/app.phpで、

..
    /*
    |--------------------------------------------------------------------------
    | Application Locale Configuration
    |--------------------------------------------------------------------------
    |
    | The application locale determines the default locale that will be used
    | by the translation service provider. You are free to set this value
    | to any of the locales which will be supported by the application.
    |
    */

    'locale' => 'ja',
    'faker_locale' => 'ja_JP',
..

と、faker_localeを設定してください。

テストでレコードの自動作成

ユニットテストでは、毎回のテストにおいてテストDBを空にしてモデルファクトリで新しいデータを埋めます。これを実行するために、Laravelで2つの異なるメカニズムが用意されています。

  • DataMigrations:Laravelのmigrationのメカニズムを使用して、毎回テストごとにDBテーブルを1から作成する
  • DatabaseTransactions:DBのトランザクションの機能を利用して、毎回テストごとにDBテーブルを空にする

違いは重要で、前者はテストDBにテーブルが何も存在しなくてもOKですが、後者では空のDBテーブルが必要です。

前者は、新規のプロジェクトで最初からLaravelのmigrationを使用してDBを管理しているならお薦めですが、既存のプログラムをLaravelに移行したプロジェクト(私の場合はこれが多い)では、違うメカニズムでDBを管理しているため前者が使用できないので後者となります。

後者では、本DBと同じDB構造とするため、以下のようにmysqldumpなどを使用してコピーする必要あります。

$ mysqludmp -u root larajapan -p -d > larajapan_test.sql
$ mysql -u root larajapan_test -p < larajapan_test.sql

ここでは、すでにmigrationの設定がなされているうえに、DatabaseMigrationsのトレイトをユニットテストの中で宣言します。

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
//use Illuminate\Foundation\Testing\DatabaseTransactions;

use App\User;

class LoginTest extends TestCase
{
    use DatabaseMigrations;
..

ログインテスト

準備整ったところで、テストしてみましょう。まずは、ログイン成功のテスト。

先ほど設定したファクトリを使用して、最初に1つだけユーザーのレコードを作成します。その後、作成したのと同じパスワードを使用してログインを実行。実行後は、認証されていることと、ホームページにリダイレクトされることを確認します。

    /** @test */
    public function valid_user_can_login()
    {
        // ユーザーを1つ作成
        $user = factory(User::class)->create([
            'password'  => bcrypt('test1111')
        ]);

        // まだ、認証されていない
        $this->assertFalse(Auth::check());

        // ログインを実行
        $response = $this->post('login', [
            'email'    => $user->email,
            'password' => 'test1111'
        ]);

        // 認証されている
        $this->assertTrue(Auth::check());

        // ログイン後にホームページにリダイレクトされるのを確認
        $response->assertRedirect('home');
    }

次はログイン失敗のテスト。ここでもファクトリで1つだけレコードを作成しますが、わざと作成したのとは違うパスワードを使用してログインして、エラーメッセージが表示されることを確認します。

    /** @test */
    public function invalid_user_cannot_login()
    {
    // ユーザーを1つ作成
        $user = factory(User::class)->create([
            'password'  => bcrypt('test1111')
        ]);

        // まだ、認証されていないことを確認
        $this->assertFalse(Auth::check());

        // 異なるパスワードでログインを実行
        $response = $this->post('login', [
            'email'    => $user->email,
            'password' => 'test2222'
        ]);

        // 認証失敗で、認証されていないことを確認
        $this->assertFalse(Auth::check());

        // セッションにエラーを含むことを確認
        $response->assertSessionHasErrors(['email']);

        // エラメッセージを確認
        $this->assertEquals('メールアドレスあるいはパスワードが一致しません',
            session('errors')->first('email'));

$responseに使用されるassertの関数は、phpunitからのassertTrueと違い、Laravelで宣言されているものです。

参照としては、本サイトの以下

https://laravel.com/docs/5.4/http-tests#available-assertions

ですが、そこには掲載されていない関数もあるので、以下もチェックしてください。

https://laravel.com/api/5.4/Illuminate/Foundation/Testing/TestResponse.html

最後に、テストを実行してみましょう。

$ vendor/bin/phpunit --filter LoginTest
PHPUnit 5.7.22 by Sebastian Bergmann and contributors.

....                                                                4 / 4 (100%)

Time: 1.09 seconds, Memory: 16.00MB

OK (4 tests, 12 assertions)

エラーもなく成功ですね!

今回のコードは以下から利用可能です。

https://github.com/lotsofbytes/larajapan/blob/l54-test/tests/Feature/LoginTest.php

ユーザー認証のテスト(1) Laravel 5.4

Laravelでのユーザー認証は私のブログの中では最も人気のあるトピックです。今回は、私のLaravelの日本語のレポジトリ(Laravel 5.4)のコードをもとに、ユーザー認証のテストに取り組んでいきます。

テストの種類

テストと言っても、いくつか種類があり、Laravel5.4からはtestsのディレクトリ構造も変わり、

tests
├── CreatesApplication.php
├── Feature
│   └── ExampleTest.php
├── TestCase.php
└── Unit
    └── ExampleTest.php

のようにFeatureUnitの2つのサブディレクトリができました。

Unitには、一般的にはユニットテストと呼ばれるもので、大方は画面の表示を伴わないModelのメソッドに対するテストを作成します。一方、Featureには、機能テストやアクセプタンステストとも言われ、複数のクラスの複数のメソッドが関わる主にコントローラの機能を検証するためで、あたかもユーザーがテストするようなテストを作成します。

私の今までの開発では、比較的作成しやすいUnitテストが主で、Featureテストは皆無に近く、人間のテスターがその仕事を行っています。人間をFeatureテストに置き換える予定はないですが、Laravelが提供するテストの環境がFeatureテストを作成しやすくなってきているので、この機会に習得しようということです。

Laravel5.4では、Featureテストのために、2つのテストのフレームワークが提供されています。

ここでは、まず、追加のパッケージのインストールも要らない、高速なHTTPテストを作成していきます。

準備

github.comにおいて以下のブランチを用意したので利用してください。

https://github.com/lotsofbytes/larajapan/tree/l54-test

インストールは以下を参照してください。

Laravelの日本語のレポジトリ(Laravel 5.4)

設定したら、必ず以下を実行してブランチを変えてください。

$ git checkout l54-test

また、そこでは、phpunit.xmlを編集も必要です。以下を参考にしてください。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="bootstrap/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>

        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="APP_URL" value="http://localhost"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="DB_HOST" value="localhost"/>
        <env name="DB_DATABASE" value="larajapan_test"/>
        <env name="DB_USERNAME" value="test"/>
        <env name="DB_PASSWORD" value="password"/>
        <env name="MAIL_DRIVER" value="log" />
        <env name="QUEUE_DRIVER" value="sync"/>
    </php>
</phpunit>

テストのDBは.envで設定しているものと違うDBが必要なことに注意してください。

最初のテスト

もとからあるtests/Feature/ExampleTest.phpのファイルを、LoginTest.phpと改名して以下のように編集します。

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class LoginTest extends TestCase
{
    /** @test */
    public function user_can_view_login()
    {
        $response = $this->get('login');

        $response->assertStatus(200);
    }

    /** @test */
    public function unauthenticated_user_cannot_view_home()
    {
        $this->get('home')
        	->assertRedirect('login');
    }
}

最初のテストuser_can_view_loginは、ログイン画面が閲覧できるかどうかのテストです。
返ってくるHTTPのステータスのコードが200なら、成功ということです。これが404(ページが見つかりません)とかだと何かがおかしいということになります。

次のテストunauthenticated_user_cannot_view_homeは、認証が必要なホームページに、認証もなしにアクセスしてみます。もちろん、アクセスできないでログイン画面にリダイレクトされるはずです。

ここ、メソッドが連結されていることに気づきましたか?

最初のテストの例のように、以下のよう2つの文に分けても書くこともできます。

$response = $this->get('home');
$response->assertRedirect('login');

これらのテストの実行ですが、Laravelのインストールにより、すでにvendorのディレクトリにphpunitのパッケージもインストールされています。

ということで、

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

...                                                                 3 / 3 (100%)
Time: 171 ms, Memory: 10.00MB

OK (3 tests, 4 assertions)

と実行してテストは皆成功となります。3 testsとあるのは、app/tests/Unit/ExampleTestがあるからです。
以下のようにフィルタを書ければ、LoginTestの中のテストだけや、user_can_view_loginの1つテストだけの実行も可能です。

$ vendor/bin/phpunit --filter=LoginTest
HPUnit 5.7.23 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 157 ms, Memory: 10.00MB

OK (2 tests, 3 assertions)

$ vendor/bin/phpunit --filter=user_can_view_login
PHPUnit 5.7.23 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 111 ms, Memory: 10.00MB

OK (1 test, 1 assertion)
Top