Laravel - Requests 說明及測試方法

Laravel 的 requests 可以將請求所需要的驗證進行分離,在這裡主要談一談 Requests 的建立方法 ,以及針對它進行測試的一些方式進行說明。

首先,建立一個 Requests

php artisan make:request TestRequest

接著開啟 App\Http\Requests\TestRequest.php

主要可以分為兩個區域, authorize及 rules

  • authorize 主要定義 request 請求需要的驗證

  • rules 主要定義 request 所要傳送的參數

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class TestRequest 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 [
            'param1' => 'required|string|max:50',
            'param2' => 'required|numeric',
        ];
    }

建立 Model

php artisan make:model Models/Test -m

建構 migration 內容如下:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTestsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('tests', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('param1');
            $table->double('param2');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('tests');
    }
}

以及 model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Test extends Model
{
    protected $fillable = ['param1', 'param2'];
}

建立 Controller

在開始前,先建立一個 resource

php artisan make:resource Test

接著建立 controller

php artisan make:controller TestController

在 controller 注入 TestRequest

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests\TestRequest;
use App\Models\Test;
use App\Http\Resources\Test as TestResource;

class TestController extends Controller
{
    public function store(TestRequest $request)
    {
        $test = Test::create($request->validated());
        return TestResource::make($test);
    }
}

建立測試

php artisan make:test App/Http/Requests/TestRequestTest

接著開啟 tests/App/Http/Requests/TestRequestTest.php

測試 requests 的方式有幾種:

test_request_without_title

test_request_without_content

<?php

namespace Tests\Feature\App\Http\Requests;

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


use App\User;
use Illuminate\Http\Response;

class TestRequestTest extends TestCase
{
    use RefreshDatabase, WithFaker;
    
    /**
     * setup factory
     *
     * @return void
     */
    protected function setUp(): void
    {
        parent::setUp();

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

    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testExample()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }

    /**
     * Test no param1
     *
     * @return void
     */
    public function testRequestFailWithNoParam1()
    {
        $response = $this->actingAs($this->user)->postJson(route('test.store'), [
                'param2' => $this->faker->numberBetween(1, 50)
            ]);
    
        $response->assertStatus(
            Response::HTTP_UNPROCESSABLE_ENTITY
        );

        $response->assertJsonValidationErrors('param1');
    }

    /**
     * Test no param1 has more then 50 charts
     *
     * @return void
     */
    public function testRequestFailWithParam1HasOver50Charts()
    {
        $response = $this->actingAs($this->user)->postJson(route('test.store'), [
                'param1' => $this->faker->paragraph(),
            ]);
    
        $response->assertStatus(
            Response::HTTP_UNPROCESSABLE_ENTITY
        );

        $response->assertJsonValidationErrors('param2');
    }


    /**
     * Test no param2
     *
     * @return void
     */
    public function testRequestFailWithNoParam2()
    {
        $response = $this->actingAs($this->user)->postJson(route('test.store'), [
                'param1' => $this->faker->word(),
            ]);

        $response->assertStatus(
            Response::HTTP_UNPROCESSABLE_ENTITY
        );

        $response->assertJsonValidationErrors('param2');
    }


    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testRequestPass()
    {
        $response = $this->actingAs($this->user)->postJson(route('test.store'), [
                'param1' => $this->faker->word(),
                'param2' => $this->faker->numberBetween(1, 50)
            ]);
        $response->assertStatus(Response::HTTP_CREATED);

        $response->assertJsonMissingValidationErrors([
            'param1',
            'param2'
        ]);
    }
}

Test Provider

建立一個 provider 範例

說明如何更優雅的方式來測試 request

php artisan make:test App/Http/Requests/TestRequestTestProvider

內容建構

<?php

namespace Tests\Feature\App\Http\Requests;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\User;
use Illuminate\Http\Response;
use App\Http\Requests\TestRequest;
use Faker\Factory;

class TestRequestTestProvider extends TestCase
{
    use RefreshDatabase;

    public function setUp(): void
    {
        parent::setUp();

        $this->validator = app()->get('validator');

        $this->rules = (new TestRequest())->rules();
    }

    public function validationProvider()
    {
        /* WithFaker trait doesn't work in the dataProvider */
        $faker = Factory::create(Factory::DEFAULT_LOCALE);

        return [
            'requestShouldFailWhenNoParam1IsProvided' => [
                'passed' => false,
                'data' => [
                    'param2' => $faker->numberBetween(1, 50)
                ]
            ],
            'requestShouldFailWhenNoParam2IsProvided' => [
                'passed' => false,
                'data' => [
                    'param1' => $faker->word()
                ]
            ],
            'requestShouldFailWhenParam1HasMoreThan50Characters' => [
                'passed' => false,
                'data' => [
                    'param1' => $faker->paragraph()
                ]
            ],
            'requestShouldPassWhenDataIsProvided' => [
                'passed' => true,
                'data' => [
                    'param1' => $faker->word(),
                    'param2' => $faker->numberBetween(1, 50)
                ]
            ]
        ];
    }

    /**
     * @test
     * @dataProvider validationProvider
     * @param bool $shouldPass
     * @param array $mockedRequestData
     */
    public function validationResultsAsExpected($shouldPass, $mockedRequestData)
    {
        $this->assertEquals(
            $shouldPass,
            $this->validate($mockedRequestData)
        );
    }

    protected function validate($mockedRequestData)
    {
        return $this->validator
            ->make($mockedRequestData, $this->rules)
            ->passes();
    }
}

問題排解

403 This action is unauthorized.

當發生 403 This action is unauthorized. 時,

表示需要有登入權限

ERR_TOO_MANY_REDIRECTS

請改用 post 及 user login 方式測試

執行測試需建立 $this->actingAs($this->user) 觸發行為

Error: Call to undefined function Tests\Feature\App\Http\Requests\assertStatus()

請檢查 $response->assertStatus 格式是否正確

參考: Testing Laravel Form Requests in a different way