gpt4 book ai didi

php - 在 Laravel 测试用例中模拟一个 http 请求并解析路由参数

转载 作者:可可西里 更新时间:2023-10-31 23:07:10 36 4
gpt4 key购买 nike

我正在尝试创建单元测试来测试一些特定的类。我用 app()->make()实例化要测试的类。所以实际上,不需要 HTTP 请求。
但是,一些经过测试的函数需要来自路由参数的信息,以便它们进行调用,例如request()->route()->parameter('info') ,这会引发异常:

Call to a member function parameter() on 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()在我的真实代码中,调用实际上位于服务提供者中。此测试用例专门用于测试该服务提供者。没有一个路由可以打印该提供程序中方法的返回值。

最佳答案

我假设您需要模拟 一个请求而不实际调度它。模拟请求到位后,您希望探测它的参数值并开发您的测试用例。

有一种未记录的方法可以做到这一点。你会感到惊讶!

问题

如您所知,Laravel 的 Illuminate\Http\Request 类(class)建立在 Symfony\Component\HttpFoundation\Request .上游类不允许您在 setRequestUri() 中手动设置请求 URI。大大地。它根据实际的请求 header 计算出来。没有其他办法。

好了,闲聊就够了。让我们尝试模拟一个请求:

<?php

use Illuminate\Http\Request;

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

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

正如你自己提到的,你会得到一个:

Error: Call to a member function parameter() on null



我们需要一个 Route
这是为什么?为什么 route()返回 null ?

看看 its implementation以及其配套方法的实现; getRouteResolver() . getRouteResolver()方法返回一个空闭包,然后 route()称它为 $route变量将是 null .然后它被返回,因此......错误。

在真实的 HTTP 请求上下文中, Laravel sets up its route resolver ,所以你不会得到这样的错误。现在您正在模拟请求,您需要自己进行设置。让我们看看如何。

<?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'));
}
}

查看创建 Route 的另一个示例来自 Laravel's own RouteCollection class .

空参数包

所以,现在你不会得到那个错误,因为你实际上有一个绑定(bind)了请求对象的路由。但它还不会起作用。如果我们此时运行 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 property越来越多。

当我们 bind the request反对路线, $parameters属性由对 bindParameters() 的后续调用填充方法依次调用 bindPathParameters() 找出特定于路径的参数(在这种情况下我们没有主机参数)。

该方法将请求的解码路径与 Symfony's 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 header 来确定请求 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可以在您的测试用例中使用的特征。

mock

即使绝对不可能像上述方法那样欺骗请求 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以及。

关于php - 在 Laravel 测试用例中模拟一个 http 请求并解析路由参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41461497/

36 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com