特定のクラスをテストするための単体テストを作成しようとしています。テストするクラスをインスタンス化するには、app()->make()
を使用します。したがって、実際にはHTTP要求は必要ありません。
ただし、テストされた関数の一部はルーティングパラメータからの情報を必要とするため、関数は呼び出しを行います。 request()->route()->parameter('info')
、そしてこれは例外をスローします:
Nullのメンバー関数parameter()への呼び出し。
私はたくさん遊んで、次のようなことを試しました:
_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()
呼び出しは、実際には実際のコードのサービスプロバイダーにあります。このテストケースは、そのサービスプロバイダーをテストするために特に使用されます。そのプロバイダーのメソッドからの戻り値を出力するルートはありません。
実際にディスパッチせずにリクエストをシミュレートする必要があると思います。シミュレートされたリクエストが用意できたら、それを調べてパラメーター値を調べ、テストケースを開発します。
これを行うには文書化されていない方法があります。あなたは驚かれることでしょう!
すでにご存じのとおり、Laravelの _Illuminate\Http\Request
_ クラスは _Symfony\Component\HttpFoundation\Request
_ に基づいています。上流のクラスでは、setRequestUri()
の方法で手動でリクエストURIを設定することはできません。実際のリクエストヘッダーに基づいて計算されます。他の方法はありません。
はい、おしゃべりで十分です。リクエストをシミュレートしてみましょう:
_<?php
use Illuminate\Http\Request;
class ExampleTest extends TestCase
{
public function testBasicExample()
{
$request = new Request([], [], ['info' => 5]);
dd($request->route()->parameter('info'));
}
}
_
あなたがあなた自身に言及したように、あなたは以下を得るでしょう:
エラー:nullのメンバー関数parameter()への呼び出し
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'));
}
}
_
Laravel独自のRoute
クラス からRouteCollection
sを作成する別の例を参照してください。
したがって、リクエストオブジェクトがバインドされたルートが実際にあるため、このエラーは発生しません。しかし、まだ機能しません。この時点でphpunitを実行すると、null
が表示されます。 dd($request->route())
を実行すると、info
パラメータ名が設定されていても、そのparameters
配列が空であることがわかります。
_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()
を呼び出して、パス固有のパラメーターを特定します(この場合、Hostパラメーターはありません)。
このメソッドは、リクエストのデコードされたパスを正規表現 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()
がパターンに一致しない_/
_を返すことです。したがって、パラメータバッグは何があっても空になります。
Request
クラスでdecodedPath()
メソッドを実行する場合、いくつかのメソッドを深く調べ、最終的に prepareRequestUri()
から値を返します。 of _Symfony\Component\HttpFoundation\Request
_。そこで、まさにその方法で、あなたはあなたの質問に対する答えを見つけるでしょう。
これは、一連のHTTPヘッダーを調べて、リクエストURIを把握しています。最初に_X_ORIGINAL_URL
_をチェックし、次に_X_REWRITE_URL
_、次に他のいくつかをチェックし、最後に_REQUEST_URI
_ヘッダーをチェックします。これらのヘッダーのいずれかを実際にリクエストURIにspoof設定して、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
_も出力します。
route
はクロージャとして実装されているため、parameter('info')
を明示的に呼び出さなくても、ルート内のルートパラメータに直接アクセスできます。これら2つの呼び出しは同じ結果を返します。
_$info = $request->route()->parameter('info');
$info = $request->route('info');
_
2番目の方法は、「info」パラメーターのモックを非常に簡単にします。
_$request = $this->createMock(Request::class);
$request->expects($this->once())->method('route')->willReturn('HelloWorld');
$info = $request->route('info');
$this->assertEquals($info, 'HelloWorld');
_
もちろん、このメソッドをテストで利用するには、request()
メソッドを通じてLaravelグローバルリクエストオブジェクトを使用する代わりに、テスト中のクラスにRequestオブジェクトを注入する必要があります。