Fre*_*ang 10 php phpunit unit-testing laravel laravel-5.3
我正在尝试创建单元测试来测试某些特定的类.我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);
Run Code Online (Sandbox Code Playgroud)
但他们都没有工作......
如何手动初始化路由器并向其提供一些路由参数?或者只是request()->route()->parameter()提供?
@Loek:你不理解我.基本上,我在做:
class SomeTest extends TestCase
{
public function test_info()
{
$info = request()->route()->parameter('info');
$this->assertEquals($info, 'hello_world');
}
}
Run Code Online (Sandbox Code Playgroud)
没有涉及"请求".该request()->route()->parameter()呼叫实际上位于我的真实代码中的服务提供商中.此测试用例专门用于测试该服务提供商.没有路由将从该提供程序中的方法打印返回值.
sep*_*ehr 21
我假设您需要模拟请求而不实际调度它.有了模拟请求,您需要探测参数值并开发测试用例.
有一种无证的方法可以做到这一点.你会感到惊讶!
正如您所知,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'));
}
}
Run Code Online (Sandbox Code Playgroud)
正如你自己提到的,你会得到一个:
错误:在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'));
}
}
Run Code Online (Sandbox Code Playgroud)
请参阅另一个Route从Laravel自己的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
}
Run Code Online (Sandbox Code Playgroud)
所以将它传递['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;
}
Run Code Online (Sandbox Code Playgroud)
问题是,当没有实际请求时,$request->decodedPath()返回/与模式不匹配的请求.所以无论如何,参数包都是空的.
如果你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'));
}
}
Run Code Online (Sandbox Code Playgroud)
令你惊讶的是,它打印出来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'));
}
}
Run Code Online (Sandbox Code Playgroud)
这也打印出来5.
我今天使用 Laravel7 遇到了这个问题,这是我解决它的方法,希望它对某人有帮助
我正在为中间件编写单元测试,它需要检查一些路由参数,所以我正在做的是创建一个固定请求以将其传递给中间件
$request = Request::create('/api/company/{company}', 'GET');
$request->setRouteResolver(function() use ($company) {
$stub = $this->createStub(Route::class);
$stub->expects($this->any())->method('hasParameter')->with('company')->willReturn(true);
$stub->expects($this->any())->method('parameter')->with('company')->willReturn($company->id); // not $adminUser's company
return $stub;
});
Run Code Online (Sandbox Code Playgroud)