Laravel テストケースで http リクエストをシミュレートし、ルートパラメータを解析する 質問する

Laravel テストケースで http リクエストをシミュレートし、ルートパラメータを解析する 質問する

特定のクラスをテストするための単体テストを作成しようとしています。app()->make()テストするクラスをインスタンス化するために を使用します。 そのため、実際には HTTP リクエストは必要ありません。

ただし、テストされた関数の中には、ルーティング パラメーターの情報が必要な関数があり、たとえば を呼び出すrequest()->route()->parameter('info')と例外がスローされます。

null でメンバー関数パラメータ() を呼び出します。

私はいろいろ試してみて、次のようなことを試しました:

request()->attributes = new \Symfony\Component\HttpFoundation\ParameterBag(['info' => 5]);  

request()->route(['info' => 5]);  

request()->initialize([], [], ['info' => 5], [], [], [], null);

しかし、どれも機能しませんでした...

ルーターを手動で初期化し、ルーティング パラメータをいくつか入力するにはどうすればよいでしょうか。または、単にrequest()->route()->parameter()使用可能にするだけでよいのでしょうか。

アップデート

@Loek: 私の言っていることが理解できなかったようです。基本的に、私がやっているのは:

class SomeTest extends TestCase
{
    public function test_info()
    {
        $info = request()->route()->parameter('info');
        $this->assertEquals($info, 'hello_world');
    }
}

「リクエスト」は関係ありません。request()->route()->parameter()実際のコードでは、呼び出しはサービス プロバイダー内にあります。このテスト ケースは、そのサービス プロバイダーをテストするために特別に使用されます。そのプロバイダーのメソッドからの戻り値を出力するルートはありません。

ベストアンサー1

必要だと思いますシミュレートするリクエストを実際にディスパッチせずに実行します。シミュレートされたリクエストを配置したら、パラメータ値を調べてテストケースを開発します。

文書化されていない方法でこれを行うことができます。驚かれると思います!

問題

すでにご存知のとおり、LaravelのIlluminate\Http\RequestクラスはSymfony\Component\HttpFoundation\Requestアップストリーム クラスでは、リクエスト URI を手動で設定することはできませんsetRequestUri()。実際のリクエスト ヘッダーに基づいて判断します。他の方法はありません。

さて、おしゃべりはもう十分です。リクエストをシミュレートしてみましょう。

<?php

use Illuminate\Http\Request;

class ExampleTest extends TestCase
{
    public function testBasicExample()
    {
        $request = new Request([], [], ['info' => 5]);

        dd($request->route()->parameter('info'));
    }
}

あなた自身が述べたように、次のものが得られます:

エラー: null でメンバー関数パラメータ() を呼び出しました

私たちにはRoute

それはなぜですか? なぜroute()返品するのですかnull?

見てその実施それに付随するメソッドの実装も同様です。getRouteResolver()メソッドgetRouteResolver()は空のクロージャを返し、route()それを呼び出すので、$route変数は になりますnull。その後、それが返されるため、エラーが発生します。

実際のHTTPリクエストコンテキストでは、Laravelはルートリゾルバを設定しますそうすれば、このようなエラーは発生しません。 リクエストをシミュレートするようになったので、自分で設定する必要があります。 方法を見てみましょう。

<?php

use Illuminate\Http\Request;
use Illuminate\Routing\Route;

class ExampleTest extends TestCase
{
    public function testBasicExample()
    {
        $request = new Request([], [], ['info' => 5]);

        $request->setRouteResolver(function () use ($request) {
            return (new Route('GET', 'testing/{info}', []))->bind($request);
        });

        dd($request->route()->parameter('info'));
    }
}

Routesを作成する別の例を見るLaravel独自のRouteCollectionクラス

空のパラメータバッグ

これで、リクエスト オブジェクトがバインドされたルートが実際に存在するため、このエラーは発生しなくなります。ただし、まだ動作しません。この時点で phpunit を実行すると、 が表示されますnull。 を実行すると、パラメーター名が設定されてdd($request->route())いるにもかかわらず、配列が空であることがわかります。infoparameters

Illuminate\Routing\Route {#250
  #uri: "testing/{info}"
  #methods: array:2 [
    0 => "GET"
    1 => "HEAD"
  ]
  #action: array:1 [
    "uses" => null
  ]
  #controller: null
  #defaults: []
  #wheres: []
  #parameters: [] <===================== HERE
  #parameterNames: array:1 [
    0 => "info"
  ]
  #compiled: Symfony\Component\Routing\CompiledRoute {#252
    -variables: array:1 [
      0 => "info"
    ]
    -tokens: array:2 [
      0 => array:4 [
        0 => "variable"
        1 => "/"
        2 => "[^/]++"
        3 => "info"
      ]
      1 => array:2 [
        0 => "text"
        1 => "/testing"
      ]
    ]
    -staticPrefix: "/testing"
    -regex: "#^/testing/(?P<info>[^/]++)$#s"
    -pathVariables: array:1 [
      0 => "info"
    ]
    -hostVariables: []
    -hostRegex: null
    -hostTokens: []
  }
  #router: null
  #container: null
}

そのため、コンストラクタ['info' => 5]に渡してもRequest何の効果もありません。Routeクラスを見て、それがどのようになっているかを見てみましょう。$parameters財産人口が増えています。

私たちがリクエストをバインドするオブジェクトをルートに追加すると、$parametersプロパティはその後の呼び出しによって設定されます。bindParameters()メソッドは次にbindPathParameters()パス固有のパラメータを把握します (この場合、ホスト パラメータはありません)。

このメソッドはリクエストのデコードされたパスを正規表現と照合します。SymfonyのSymfony\Component\Routing\CompiledRoute(上記のダンプでもその正規表現を確認できます)、パス パラメータに一致するものを返します。パスがパターンと一致しない場合は空になります (これが今回のケースです)。

/**
 * Get the parameter matches for the path portion of the URI.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return array
 */
protected function bindPathParameters(Request $request)
{
    preg_match($this->compiled->getRegex(), '/'.$request->decodedPath(), $matches);
    return $matches;
}

問題は、実際のリクエストがない場合、パターンに一致しないが$request->decodedPath()返されることです。そのため、パラメータ バッグは、何があっても空になります。/

リクエストURIの偽装

decodedPath()このクラスのメソッドをたどっていくとRequest、いくつかのメソッドを深く理解し、最終的に値を返すことになります。prepareRequestUri()Symfony\Component\HttpFoundation\Request。まさにその方法で、あなたの質問に対する答えが見つかります。

一連のHTTPヘッダーを調べてリクエストURIを判別します。まず をチェックしX_ORIGINAL_URL、次にをチェックしX_REWRITE_URL、次に他のいくつかをチェックし、最後に ヘッダーをチェックしますREQUEST_URI。これらのヘッダーのいずれかを実際に設定することができます。偽装リクエストURIと達成最小http リクエストのシミュレーション。見てみましょう。

<?php

use Illuminate\Http\Request;
use Illuminate\Routing\Route;

class ExampleTest extends TestCase
{
    public function testBasicExample()
    {
        $request = new Request([], [], [], [], [], ['REQUEST_URI' => 'testing/5']);

        $request->setRouteResolver(function () use ($request) {
            return (new Route('GET', 'testing/{info}', []))->bind($request);
        });

        dd($request->route()->parameter('info'));
    }
}

驚いたことに、パラメータ5の値が出力されます。info

掃除

機能をヘルパーsimulateRequest()メソッド、またはSimulatesRequestsテスト ケース全体で使用できる特性に抽出する必要がある場合があります。

嘲笑

上記の方法のようにリクエスト URI を偽装することが絶対に不可能であったとしても、リクエスト クラスを部分的にモックし、予想されるリクエスト URI を設定することは可能です。次のようになります。

<?php

use Illuminate\Http\Request;
use Illuminate\Routing\Route;

class ExampleTest extends TestCase
{

    public function testBasicExample()
    {
        $requestMock = Mockery::mock(Request::class)
            ->makePartial()
            ->shouldReceive('path')
            ->once()
            ->andReturn('testing/5');

        app()->instance('request', $requestMock->getMock());

        $request = request();

        $request->setRouteResolver(function () use ($request) {
            return (new Route('GET', 'testing/{info}', []))->bind($request);
        });

        dd($request->route()->parameter('info'));
    }
}

これ5も印刷されます。

おすすめ記事