見出し画像

laravel (sail)でテストしてまっか?(Featureテスト準備編)

テストをやるには前提が必要やからね

<?php

namespace App\Http\Controllers;

use App\Models\UploadedFile;
use Illuminate\Http\Request;

class UploaderController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        $uploadedFiles = UploadedFile::latest()->get();
        return view('uploaders.index', ['uploadedFiles' => $uploadedFiles]);
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        $request->validate([
            'file' => 'required|file',
        ]);
        $file = $request->file('file');

        $originalName = $file->getClientOriginalName();
        $mime = $file->getMimeType();
        $size = $file->getSize();
        $savedName = $savedName = \Str::random(10).md5($originalName);
        $data = [
            'original_name' => $originalName,
            'saved_name'    => $savedName,
            'mime_type'     => $mime,
            'size'          => $size,
        ];

        \DB::beginTransaction();
        $uploadedFile = UploadedFile::create($data);
        $extension = $file->getClientOriginalExtension();
        $savedName = sprintf('%05d.%s', $uploadedFile->id, $extension);
        $path = $file->storeAs('uploaded_files', $savedName, 'public');

        // saved_nameを更新
        $uploadedFile->update(['saved_name' => $savedName]);
        \DB::commit();

        // dd($request->all());
        return redirect(route('uploaders.index'))
            ->with(['status' => __('File uploaded')]);
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(UploadedFile $uploader)
    {
        \Storage::delete('uploaded_files/' . $uploader->saved_name);
        $uploader->delete();
        return redirect()->route('uploaders.index')
                         ->with('status', __('File deleted successfully.'));
    }
}

こういうコントローラーのlaravelのコードがある。まあこれ単純にファイルを保存して表示して削除するたけなんだけど、ちょっとこれでは捻りが足りないからvalidationなんかも付けてみよう。

ValidationのためのForm requestの追加

これは簡単で

artisan make:request UploadedFileRequest

とすると

app/Http/Requests/UploadedFileRequest.php というファイルができるから開く

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UploadedFileRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true; // trueにする
    }
// 略

まずdefaultでfalseなので、このままだと使えないからtrueにしておく。実はここで複雑な権限のの場合を定義しておいて、それをテストしておいてもいいんだけど、ちょっとコードが複雑になるので、ここでは実行しない。

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'file' => 'required|file|image|max:300', // 300KB以下の画像
        ];
    }

ここにruleを書く。ここでは「300キロバイト以下の画像ファイルが必須である」事を示している。validationエラーのメッセージもカスタムできるんだけど、今回はdefaultのまんまにしといた(このメッセージも、もちろんテスト可能である)。

controllerに組みこむ

app/Http/Controllers/UploaderController.php 

use App\Http\Requests\UploadedFileRequest;

これをuseしておいて

    // public function store(Request $request)
    public function store(UploadedFileRequest $request)

と差し替えるだけだ。これで

viewもよるが、こんな感じになる。今回はこのvalidationとかを中心にテストを仕掛けていってみよう。

の前に現状のテストを確認する

まず、tests/ディレクトリを覗いてみよう

% find tests
tests
tests/Unit
tests/Unit/ExampleTest.php
tests/TestCase.php
tests/CreatesApplication.php
tests/Feature
tests/Feature/Auth
tests/Feature/Auth/RegistrationTest.php
tests/Feature/Auth/PasswordConfirmationTest.php
tests/Feature/Auth/AuthenticationTest.php
tests/Feature/Auth/PasswordResetTest.php
tests/Feature/Auth/EmailVerificationTest.php
tests/Feature/Auth/PasswordUpdateTest.php
tests/Feature/ExampleTest.php
tests/Feature/ProfileTest.php

このように、UnitとFeatureに大きく2分類される。ここではFeatureに沢山入っているのがわかるが、これはLaravel Breezeが置いていったものだ。というわけでここではUnitテストは放置して、Featureテストばっかり見ていくことにしよう。

既存のコードを少し眺めてみる

たとえば

tests/Feature/ExampleTest.php これに関してはBreezeが置いていったわけでなく最初からあるテストの雛形である

<?php

namespace Tests\Feature;

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

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     */
    public function test_the_application_returns_a_successful_response(): void
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}

これはトップページ(「 / 」 )にアクセスしたときにhttpのステータスコードが200が返る事を期待している。

laravel sailで実行する

テストにおいてはlaravel sailがあるのかないのかで随分変わってきてしまう。sailが無い場合はテスト用のデーターベースを1つ起動する必要がある(あるいは、sqliteでテストするという事もある)。どういう挙動になるのかは、後で説明するとして、ここではsailを使っているとして、設定方法を解説していく。

まずdocker-compose.ymlを見てみよう

mysqlのセクションであるが

    mysql:
        image: 'mysql/mysql-server:8.0'
        ports:
            - '${FORWARD_DB_PORT:-3306}:3306'
        environment:
            MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
            MYSQL_ROOT_HOST: '%'
            MYSQL_DATABASE: '${DB_DATABASE}'
            MYSQL_USER: '${DB_USERNAME}'
            MYSQL_PASSWORD: '${DB_PASSWORD}'
            MYSQL_ALLOW_EMPTY_PASSWORD: 1
        volumes:
            - 'sail-mysql:/var/lib/mysql'
            - './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
        networks:
            - sail
        healthcheck:
            test:
                - CMD
                - mysqladmin
                - ping
                - '-p${DB_PASSWORD}'
            retries: 3
            timeout: 5s

よく見るとこんなのが仕込まれている

./vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh

これはsailでmysqlシェルにアクセスすると確認可能である

% ./vendor/bin/sail mysql
show databases;show databases;Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 326
Server version: 8.0.32 MySQL Community Server - GPL

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| performance_schema |
| simple_uploader    |
| testing            |
+--------------------+
4 rows in set (0.00 sec)

要するにテストでデータベースを使う場合はこのtestingを使えといっているのだ。従ってそのように書かないといけない。

.env.testingの作成

一応基本的にはガバっと.envを.env.testingにcpする

% cp .env .env.testing
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=testing
DB_USERNAME=simple_uploader
DB_PASSWORD=password

このようにDB_DATABASEにtestingをセットすればok。

そうすると、このようにテストが通るようになる

% ./vendor/bin/sail artisan test tests/Feature/ExampleTest.php

   PASS  Tests\Feature\ExampleTest
  ✓ the application returns a successful response                                                                0.26s

  Tests:    1 passed (1 assertions)
  Duration: 0.33s

ただ、ここではbreezeを使った認証を利用していないので、全てのテストを通すと大量のエラーが出てくるはずだ。

 % ./vendor/bin/sail artisan test tests/Feature

   FAIL  Tests\Feature\Auth\AuthenticationTest
  ⨯ login screen can be rendered                                                                                 0.65s
  ⨯ users can authenticate using the login screen                                                                0.05s
  ✓ users can not authenticate with invalid password                                                             0.02s
  ⨯ users can logout                                                                                             0.02s

   FAIL  Tests\Feature\Auth\EmailVerificationTest
  ⨯ email verification screen can be rendered                                                                    0.02s
  ⨯ email can be verified                                                                                        0.02s
  ⨯ email is not verified with invalid hash                                                                      0.01s

   FAIL  Tests\Feature\Auth\PasswordConfirmationTest
  ⨯ confirm password screen can be rendered                                                                      0.02s
  ⨯ password can be confirmed                                                                                    0.02s
  ⨯ password is not confirmed with invalid password                                                              0.02s

   FAIL  Tests\Feature\Auth\PasswordResetTest
  ⨯ reset password link screen can be rendered                                                                   0.01s
  ⨯ reset password link can be requested                                                                         0.02s
  ⨯ reset password screen can be rendered                                                                        0.02s
  ⨯ password can be reset with valid token                                                                       0.02s

   FAIL  Tests\Feature\Auth\PasswordUpdateTest
  ⨯ password can be updated                                                                                      0.02s
  ⨯ correct password must be provided to update password                                                         0.02s

   FAIL  Tests\Feature\Auth\RegistrationTest
  ⨯ registration screen can be rendered                                                                          0.01s
  ⨯ new users can register                                                                                       0.01s

   PASS  Tests\Feature\ExampleTest
  ✓ the application returns a successful response                                                                0.03s

   FAIL  Tests\Feature\ProfileTest
  ⨯ profile page is displayed                                                                                    0.02s
  ⨯ profile information can be updated                                                                           0.02s
  ⨯ email verification status is unchanged when the email address is unchanged                                   0.02s
  ⨯ user can delete their account                                                                                0.02s
  ⨯ correct password must be provided to delete account                                                          0.02s
  ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Auth\AuthenticationTest > login screen can be rendered
  Expected response status code [200] but received 404.
Failed asserting that 404 is identical to 200.

  at tests/Feature/Auth/AuthenticationTest.php:18
     14public function test_login_screen_can_be_rendered(): void
     15▕     {
     16▕         $response = $this->get('/login');
     17▕
  ➜  18▕         $response->assertStatus(200);
     19▕     }
     2021public function test_users_can_authenticate_using_the_login_screen(): void
     22▕     {


//  略

これは、今認証を利用していないので削除してしまってもいい

% rm -rf tests/Feature/Auth
% tests/Feature/ProfileTest.php # プロフィールも使っていないので削除

ただまあこれはテストの書き方としては秀逸なのでこれを利用してテストの内容を見ておくのもいいんですけどね。っていう。

% ./vendor/bin/sail test

   PASS  Tests\Unit\ExampleTest
  ✓ that true is true                                                                                            0.01s

   PASS  Tests\Feature\ExampleTest
  ✓ the application returns a successful response                                                                0.26s

  Tests:    2 passed (2 assertions)
  Duration: 0.37s

このようにテストが通るようになった。

次回は

いよいよテストを書いていくことにしよう










この記事が気に入ったらサポートをしてみませんか?