Laravelテストで、DataProvider/データプロバイダーの使い方

LaravelのテストでDataProviderを使っていますでしょうか。今回は、DataProviderとは。効果について述べていきます。

0-1. DataProviderとは

Laravel Unitテストにおけるテスト用のデータを生成する仕組みです。Unitテストには、様々なデータ生成の仕組みが存在します。そのうちの1つです。

0-2. DataProviderのメリット

では、様々なデータ生成の仕組みが存在するなかで、DataProviderのメリットはなんでしょうか。それは同じテストにおいて、データのみが異なる場合に非常に簡単に実行してくれます。

例えば、動物かどうかを確認するテスト を実行する際に。
確認するデータ(猿・像・ひまわり・ライオン ) に対して毎回テストを書いていたら面倒ですよね?? これをDataProviderを使えば簡単に全データをテストしてくれます。

1. DataProviderの書き方

1-1. DataProviderのメソッドを書く

> tests/AnimalTest.php

public function test_check_animal(){
//実行されるテスト
}


//これがDataProviderです。
public function animalTestData(){
        return [
            '猿'       => ['肺呼吸', 'move', '光合成ナシ'],
            '像'       => ['肺呼吸', 'move', '光合成ナシ'],
            'ひまわり'  => ['気孔と樹皮', 'stay', '光合成アリ'],
            'ライオン'  => ['肺呼吸', 'move', '光合成ナシ'],
        ];
}

注意点

  1. アクセス修飾子は public
  2. returnの中は連想配列で

1-2. DataProviderをテストメソッドで呼び出す

public function test_check_animal() でDataProviderを使ってみます。

> tests/AnimalTest.php

/**
 * @dataProvider animalTestData
 */
public function test_check_animal(string $breathing, string $move, bool $photosynthesis){
//実行されるテスト

}


//これがDataProviderです。
public function animalTestData(){
        return [
            '猿'       => ['肺呼吸', 'move', '光合成ナシ'],
            '像'       => ['肺呼吸', 'move', '光合成ナシ'],
            'ひまわり'  => ['気孔と樹皮', 'stay', '光合成アリ'],
            'ライオン'  => ['肺呼吸', 'move', '光合成ナシ'],
        ];
}

注意点

  1. DataProviderを利用するテストメソッドの上で、利用するDataProviderを宣言します。
    • @dataProvider animalTestData で宣言している部分です。
  2. テストメソッドでDataProviderのデータを引数で受け取ります。
    • string $breathing, string $move, bool $photosynthesis で引き受けている部分です。

以上です。

猿・像・ひまわり・ライオン の4種類のテストが1つのテストメソッドで実行出来ます。

UnitTest/Laravel5.xから6.xへ変更 | Method ‘XXXX’ is not compatible with method ‘Tests\TestCase::setUp()’.intelephense(1038)

Laravel5.xから6.x へバージョン変更した際に発生したUnitテストのError対処法です。

1. Errorメッセージ

Method 'XXXX' is not compatible with method 'Tests\TestCase::setUp()'.intelephense(1038)

2. Error箇所

    public function setUp(){ // ここでError
        parent::setUp();
    }

3. Errorの意味

'Tests\TestCase::setUp()'と互換性がありません。

どういうことでしょうか?

setUp関数は、use Tests\TestCase; クラスから参照されています。(正確には、更に継承先ののvendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php

ここで記述されている setUp関数を確認すると。

    protected function setUp(): void
    {
         //省略
    }

返り値 void が必須になっています。

4. 修正方法

    public function setUp(): void{ // 返り値を継承先に合わせる
        parent::setUp();
    }

setUp()関数の返り値の型を継承先に合わせてあげます。

これで解決します。

Laravel:privateメソッドに対してUnitテストを実施する方法

privateメソッドに対してUnitテストを実施する方法を紹介します。

0. 前提知識

アクセス修飾子をご存知でしょうか。 class内のメソッド・プロパティに対して外部からのアクセス要件を設定する情報です。public, protected, private とあります。 privateは同一Classからしかアクセス出来ません。

つまり、Unitテストは他のClassから実行するものなので、通常のUnitテストではprivateメソッドに対してテストは実行出来ません。

では、どうやってUnitテストを実施していくのでしょうか。

Laravelには、privateメソッドに対してUnitテストを実行できる環境が用意されているので、そちらを使います。

1. ReflectionClassを利用する

実装方法は非常にシンプルです。

ExampleService内にあるprivateMehtod をUnitテストのPrivateMethodTest で実行します。

ExampleService はざっくりと下記

<?php
class ExampleService 
{
    //省略
    private function privateMehtod() // privateメソッドである
    {
        // メソッド内処理
        return $result;
    }
    //省略
}

UnitテストPrivateMethodTest は下記です。 ReflectionClass の使い方を参照下さい。 下記にて、test_example() にてprivateMehtod を実行してreturnを返しています。

<?php

use ReflectionClass;
use ExampleService;

class PrivateMethodTest extends TestCase
{
    private $reflection;
    private $service;

    public function setUp()
    {
        parent::setUp();
        $this->service = new ExampleService();
        $this->reflection = new ReflectionClass($this->service);
    }

    public function test_example()
    {
        $example_class = new ExampleService();

        $method = $this->reflection->getMethod( 'privateMehtod' );
        $method->setAccessible( true );
        $result = $method->invoke( $this->service );
        // assertでTest確認
        // 省略
    }
}

2. privateメソッドに引数がある場合

privateMehtod に引数 $params がある場合

<?php
class ExampleService 
{
    //省略
    private function privateMehtod( $params ) // privateメソッドである
    {
        // メソッド内処理
        return $result;
    }
    //省略
}

変更箇所で記述しています。

  • invoke( ) -> invokeArgs( )
  • invokeArgs( ) の第2引数に$params を代入
    • 第2引数 は array型で渡す必要があります。
<?php

use ReflectionClass;
use ExampleService;

class PrivateMethodTest extends TestCase
{
     // 省略

    public function test_example()
    {
        // 省略
        $result = $method->invokeArgs( $this->service, [$params] );
        // 省略
    }
}

以上です。

ReflectionMethodを使う方法もありますが、今回はReflectionClass を使う方法を紹介しました。

Laravel5.2でのUnitテスト実行コマンド

物凄くニッチなテーマですが。

Readoubleにもコマンドが書かれていなかったので、備忘録です。

1. アプリフォルダへ移動

2. testsディレクトリ全てのテストを実行

./vendor/bin/phpunit

3. 特定ファイルのテストを実行

./vendor/bin/phpunit tests/テストのファイル名

SQSのメッセージをトリガーにLambdaを起動

以前、Laravelを利用してSQSメッセージを送信する方法を解説しました。こちらの続きです。

SQSメッセージを送信するだけで機能完了することはほぼ無いことだと思います。メッセージを送信した先で、何らかの処理を実行するために用います。
そこでよく使われるのが、Lambdaです。

SQSメッセージをトリガーにして、Lambdaを起動させるまでを簡潔に解説していきます。

0. GOAL

今回のGOALは、SQSのメッセージ送信をトリガーにして、Lambdaを起動させる。そして、起動したことをCloudWatchで確認。

1. Lambdaを作成

1-1. Lambda関数を新規作成

特別な設定は不要です。 今回、Python3.7 で作成していきます。

1-2. Roleを設定

Roleを設定していきます。 SQSをポーリングして、メッセージを受信できるRoleを設定します。

AWSLambdaSQSQueueExecutionRoleを追加します。

1-3. 関数のコードを設定

import json
def lambda_handler(event, context):
    for record in event['Records']:
       payload=record["body"]
       print(str(payload))

CloudWatchへ、SQSのメッセージを送信するだけなので、record["body"]  をprintする。だけの単純な処理です。

2. SQSメッセージ送信をトリガーに設定する

トリガーをLmabdaに設定します。

  • SQSを選択
  • キューを指定

キューを設定することで、キューにメッセージが受信することをトリガーにLambdaが起動されます。

CloudWatchにSQSメッセージがprintされています。確認してみて下さい。

Q. LambdaはSQSメッセージをどのように判定しているのか.

今回、SQSはメッセージを送信していません。その上で、LambdaではトリガーにSQSのキューを指定しただけです。

結論から言うと、LambdaはSQSのメッセージを定期的に確認しにいってます。これをポーリングと言います。

下記ブログで詳しく書いてありましたので、参照してみて下さい。

実は月額30円の定額課金?SQS⇒lambdaの罠

LaravelでAWS/SQSクライアントを作成し、メッセージを送信

AWSのキューイングサービスであるSQSを利用する方法を解説します。

0. AWSアカウントを作成し、SQSでキューを作成

AWSアカウントの作成方法は省略します。

SQSのキュー作成で、1点だけ注意点です。
キュータイプ標準 で作成して下さい。

1. LaravelにAWS-SDKをインストール

AWS-SDKとは、AWSとのAPI通信を簡素かしたパッケージです。LaravelでAWSを利用する際には、aws-sdk-php-laravel を利用する機会が多いかと思います。

すごい簡単にいうと、AWSとの面倒な認証やあれこれをやってくれる処理郡です。

1-1. composer.json へ aws-sdk-php-laravel を追記

composer.json

{
    "require": {
        //省略
        "aws/aws-sdk-php-laravel": "~3.0"
        //省略
    }
}

1-2. aws-sdk-php-laravel をインストール

composer update を実行する。

composer update

実行が完了すると
/vendor/aws 配下にAWS-SDKがインストールされています。

2. Laravel で SQS クライアントを作成し、メッセージを送信する。

Controllerで、SQSキューにメッセージを送信していきます。まずは、SQSのクライアントを作成します。

2-1. SqsClientを作成する

WebApiControllerのsendSqsメソッドの中で、処理を書いてきます。

<?php

namespace App\Http\Controllers;
use Aws\Sqs\SqsClient;

class WebApiController extends Controller
{
    public function sendSqs()
    {
        $config = [
            'credentials' => [
                'key' => env('AWS_SECRET_ACCESS_KEY'),
                'secret' => env('AWS_ACCESS_KEY_ID'),
            ],
            'region' => 'ap-northeast-1', // ご利用のリージョン
            'version' => 'latest',
        ];
        //SQS Clientを作成
        $sqs = SqsClient::factory($config);
    }
}

SqsClientaws-sdk-php-laravel をインストールした際に一緒に含まれています。

$sqs でSQSのClientを作成出来ました。次に $sqs を利用してメッセージを送信していきます。

2-2. SQSにメッセージを送信

上記で作成したクライアントを利用してメッセージを送信します。

<?php

namespace App\Http\Controllers;
use Aws\Sqs\SqsClient;

class WebApiController extends Controller
{
    public function sendSqs()
    {
        $config = [
            'credentials' => [
                'key' => env('AWS_SECRET_ACCESS_KEY'),
                'secret' => env('AWS_ACCESS_KEY_ID'),
            ],
            'region' => 'ap-northeast-1', // ご利用のリージョン
            'version' => 'latest',
        ];
        //SQS Clientを作成
        $sqs = SqsClient::factory($config);

        // SQSのメッセージ
        $value = "piyo";

        // 送りたいSQSのURLを指定
        $sqs_url = env('AWS_SQS_URL');

        $params = [
            'DelaySeconds' => 0,
            'MessageBody' => $value,
            'QueueUrl' => $sqs_url,
        ];

        $result = $sqs->sendMessage($params);
    }
}

上記で、SQSのキューにメッセージを送信出来ます。

Q. SQSのURLはどこにある?

$sqs_url = env('AWS_SQS_URL');

こちらに SQSのURLを設定する必要があります。

それは、キューの画面へいくと確認出来ます。

以上です。

Eagerロードとは。N+1問題を解決する

Eagerロードとは。に関して簡潔に回答している情報があまりないと感じています。

公式サイトには下記と記述されていますが。初見の人が意味を理解できるのか…少し怪しいと思います。

プロパティとしてEloquentリレーションへアクセスすると、関連するモデルは「遅延読み込み」されます。つまりこれは、最初にプロパティへアクセスするまで、リレーションデータが実際にロードされないことを意味します。ただし、Eloquentは、親モデルにクエリを実行するときに、関係を「Eager(積極的)ロード」できます。Eagerロードにより、「N+1」クエリの問題が軽減されます。N+1クエリの問題を説明するために、Authorモデルに「属する(belongs to)」Bookモデルについて考えてみます。

https://readouble.com/laravel/8.x/ja/eloquent-relationships.html#eager-loading

Eagerローディング とは。

簡潔に回答すると、下記かなと思います。

積極的なローディング。
Eloquentを利用したデータ取得に際して、関連する(リレーション先)データを一緒に取得してくる手法のこと。

Eagerローディングを利用することで、N+1問題の解消が可能です。

以前、孫リレーション先に対して、N+1問題を解消する方法を解説しているので、合わせてご確認頂けたらと思います。

Laravelで使うクロージャー/Closure/無名関数

クロージャー、またの名を無名関数と言います。
初学者の方には理解しづらい概念なのでは無いでしょうか。
私もクロージャーを利用する機会はありますが、正しく理解せずに利用していました。

様々な記事で紹介されていますが、いまいち分かりづらいですよね…. 

おそらく、理解を難しくしている1番の原因は「何となく分かるが、何のために存在しているのか分からない」ということに尽きると思います。

今回は、クロージャーが何のために存在するのか。また、Laravelだとどのように使われているのかを紹介していきます。 少しでも、クロージャーのイメージを掴んで頂けたらと思います。

Q1. クロージャー(無名関数)とは

名称の通り無名関数です。名もなき関数。
ただの処理の塊です。

Q2. クロージャー(無名関数)の書き方

function( 引数 ){ 
//処理
};

この部分がクロージャー(無名関数)です。名が存在しません。

通常のLaravel関数であれば下記のように、hoge、piyoなどの関数名が付きます。が、クロージャーは名もなき処理の塊です。

private function hoge(){
//処理
}
private function piyo(){
//処理
}

Q3. クロージャーの利用用途

色々な使われ方をします。
が、そこを全て把握しようとすると理解を難しくすると思います。

ここでは、Laravelでよく利用されている使われ方を利用することによって、敢えて限定的な利用用途を紹介します。

Q3-1. Laravelでよく使われるクロージャーの利用用途

コールバックの処理に使われるのが、クロージャー(無名関数)の代表的な利用用途だと思います。

コールバック処理での、クロージャー利用用途を理解するとクロージャーの存在価値がよく分かると思います。

Q3-2. Laravelでコールバックで使われるクロージャー利用例

実際に、Laravelのコールバック処理内で利用されるクロージャーの利用例を紹介します。

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

必ず利用するRouteの処理もクロージャーを利用しています。
Route::get( 第一引数 , 第二引数 ); という処理です。

  • 第一引数:
    • ルーティングの文字列
  • 第二引数:
    • ルーティングに応じた処理(viewを返す OR Controllerのメソッドを呼び出す) -> クロージャーの処理で記述
Routeの深堀り

Route::get( ); の処理は下記で記述されています。

// ディレクトリ
app/vendor/laravel/framework/src/Illuminate/Routing/Router.php

//メソッド
    /**
     * Register a new GET route with the router.
     *
     * @param  string  $uri
     * @param  array|string|callable|null  $action
     * @return \Illuminate\Routing\Route
     */
    public function get($uri, $action = null)
    {
        return $this->addRoute(['GET', 'HEAD'], $uri, $action);
    }

addRoute()に $uri, $actionが渡されています。
$action → クロージャー(無名関数)で渡している処理となっています。

addRoute をもう少し深掘る
//addRoute
    public function addRoute($methods, $uri, $action)
    {
        return $this->routes->add($this->createRoute($methods, $uri, $action));
    }

//createRoute
protected function createRoute($methods, $uri, $action)
{

    if ($this->actionReferencesController($action)) {
        $action = $this->convertToControllerAction($action);
    }

    $route = $this->newRoute(
        $methods, $this->prefix($uri), $action
    );

    if ($this->hasGroupStack()) {
        $this->mergeGroupAttributesIntoRoute($route);
    }

    $this->addWhereClausesToRoute($route);

    return $route;
}

こちらのRouterの処理を全てを理解する必要はありません。

抑えておきたいのは、Route::get( 第一引数 , 第二引数 )で引き渡した、クロージャーが、$actionとして、最終的に$routeインスタンスに代入され、その後の処理に続いていくということです。

このように、クロージャーは処理自体をコールバック処理の中に含めて渡していくために用いられるイメージを持つと、存在価値がイメージ掴みやすいかと思います。

Q3-3. Laravelでコールバックで使われるクロージャー処理を言語化すると…

イメージしやすくするために、敢えて 強引に言語化すると

  • 処理が途中まで決まっている(コールバック)処理
  • 処理の一部分だけ、利用者(プログラム記述者)が勝手に決めて、処理を渡して!
  • その渡す処理を クロージャー(無名関数)で渡して!

みたいな感じです。

Q4. クロージャーのまとめ

クロージャーの利用用途は幾つもありますが、今回はコールバック処理の中で利用されるクロージャーのみを紹介しました。
コールバック内での利用され方は、下記でした。

処理が途中まで決まっている(コールバック)処理
処理の一部分だけ、利用者(プログラム記述者)が勝手に決めて、処理を渡して!
その渡す処理を クロージャー(無名関数)で渡して!

ざっくりと、まとめると。

定形の処理の一部を、可変にして自由に利用させたい。
その可変部分の処理を名もなきクロージャー(無名関数)で記述して下さい。

という感じです。

イメージしやすくするために省略した部分もありますが、イメージを掴めたでしょうか?

[Nuxt×Laravel] API通信のValidationをLaravelで処理する方法

Nuxt×Laravel でアプリケーションを作成する際に、バックエンド側のLaravelでValidationを設定する方法を紹介します。

幾つか設定方法がありますが、今回は下記の方法にてValidationを設定していきます。

  1. Laravel >> FormRequestを作成
  2. Laravel >> FormRequestのrulesを設定
  3. Laravel >> FormRequest内で、failedValidationをオーバーライド
  4. Laravel >> Postリクエスト内にFormRequestを適用
  5. Nuxt >> APIレスポンスにて、Validationメッセージを取得

0. 前提

NuxtからのAPIリクエストは axiosを利用しています。axiosに利用方法に関して詳細は省きます。

1. Laravel >> FormRequestを作成

コマンドで生成します。

php artisan make:request PostRequest // PostRequest部分は任意. FormRequestの命名

コマンドによって下記ファイルが生成されます。

server/app/app/Http/Requests/PostRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class PostRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
        ];
    }
}

2. Laravel >> FormRequestのrulesを設定

Validationのrulesを設定していきます。

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class PostRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true; // trueへ変更
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'user_id' => 'required',
            'post_id' => 'required',
        ];
    }
}
'user_id'、'post_id'が必須というシンプルなrulesにしておきます。

3. Laravel >> FormRequest内で、failedValidationをオーバーライド

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class PostRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true; // trueへ変更
}

/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'user_id' => 'required',
'post_id' => 'required',
];
}

/**
* [override] バリデーション失敗時ハンドリング
*
* @param Validator $validator
* @throw HttpResponseException
* @see FormRequest::failedValidation()
*/
protected function failedValidation( Validator $validator )
{
$response['data'] = [];
$response['status'] = 'NG';
$response['summary'] = 'Failed validation.';
$response['errors'] = $validator->errors()->toArray();

throw new HttpResponseException(
response()->json( $response, 422 )
);
}
}

FormRequestファイル内に、failedValidationを追加しました。

これによって、下記のファイル > メソッドをオーバーライドします。

server/app/vendor/laravel/framework/src/Illuminate/Foundation/Http/FormRequest.php > failedValidation()

また、failedValidation()がいつ呼び出されるかというと。下記です。

server/app/vendor/laravel/framework/src/Illuminate/Validation/ValidatesWhenResolvedTrait.php > validateResolved()

4. Laravel >> Postリクエスト内にFormRequestを適用

FormRequestをContollerのメソッドに適用します。

    public function postAction(PostRequest $request) { // 適用
        //省略
        //user_id, post_id が受け取る
    }

apiのroute

server/app/routes/api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;


/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::post('postAction', [任意のController::class, 'postAction']);
    
});

5. Nuxt >> APIレスポンスにて、Validationメッセージを取得

const payload = {
  user_id: XXXX,
  post_id: YYYY,
};
const access_token = ZZZZ;
this.$axios.$post('/api/postAction', payload , { headers: { Authorization: `Bearer ${access_token}` } })
    .then(
      res => {
          console.log(res);
      }
    )
    .catch(error => {
      console.log(error.response.data);
      })

APIリクエスト/レスポンスの処理のみ記載しています。

レスポンスを受け取る際は、error.response で受け取ります。