草庐IT

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

coder 2023-12-31 原文

我正在尝试创建单元测试来测试一些特定的类。我用 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/

有关php - 在 Laravel 测试用例中模拟一个 http 请求并解析路由参数的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  3. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  4. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  5. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  6. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  7. ruby - Ruby 的 Hash 在比较键时使用哪种相等性测试? - 2

    我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。

  8. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  9. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  10. ruby-on-rails - Rails HTML 请求渲染 JSON - 2

    在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这

随机推荐