Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/238.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在Laravel中使用PHPUnit测试HTTP动词时遇到问题_Php_Unit Testing_Laravel_Laravel 5_Laravel Middleware - Fatal编程技术网

在Laravel中使用PHPUnit测试HTTP动词时遇到问题

在Laravel中使用PHPUnit测试HTTP动词时遇到问题,php,unit-testing,laravel,laravel-5,laravel-middleware,Php,Unit Testing,Laravel,Laravel 5,Laravel Middleware,在开始之前,我想说我已经看过了。不要让这些名字欺骗你;他们是两个不同的职位。这两篇文章都超过9个月了,没有好的答案。 我在测试路线时遇到了一些问题。因此,我在运行OS X Yosemite 10.10.2的Mac Mini上制作了一个新的Laravel 5项目。我正在制作一个API。因此,我首先开始制作API的后端部分。我决定使用PHPUnit为这个项目实现TDD。我的PHPUnit版本是4.0.20。我正在运行PHP5.5.14,我正在使用artisan命令行工具中与Laravel打包的服务器

在开始之前,我想说我已经看过了。不要让这些名字欺骗你;他们是两个不同的职位。这两篇文章都超过9个月了,没有好的答案。

我在测试路线时遇到了一些问题。因此,我在运行OS X Yosemite 10.10.2的Mac Mini上制作了一个新的
Laravel 5
项目。我正在制作一个API。因此,我首先开始制作API的后端部分。我决定使用
PHPUnit
为这个项目实现TDD。我的
PHPUnit
版本是
4.0.20
。我正在运行
PHP5.5.14
,我正在使用
artisan
命令行工具中与
Laravel
打包的服务器。
现在,我开始确保我可以直接连接到服务器。因此,我的第一个测试看起来像:

public function testGetMessagesResponse()
{
    $response = $this->call('GET', 'Messages');

    $this->assertEquals(200, $response->getStatusCode());
}
public function testPostMessagesResponse()
{
    $response = $this->call('POST', 'Messages');

    $this->assertEquals(200, $response->getStatusCode());
}
1) MessagesTest::testPostMessagesResponse
Failed asserting that 500 matches expected 200.
这一切都很顺利。然而,当我转到POST请求时,它失败了。这是我的第二次测试的样子:

public function testGetMessagesResponse()
{
    $response = $this->call('GET', 'Messages');

    $this->assertEquals(200, $response->getStatusCode());
}
public function testPostMessagesResponse()
{
    $response = $this->call('POST', 'Messages');

    $this->assertEquals(200, $response->getStatusCode());
}
1) MessagesTest::testPostMessagesResponse
Failed asserting that 500 matches expected 200.
这个测试失败了。以下是输出结果:

public function testGetMessagesResponse()
{
    $response = $this->call('GET', 'Messages');

    $this->assertEquals(200, $response->getStatusCode());
}
public function testPostMessagesResponse()
{
    $response = $this->call('POST', 'Messages');

    $this->assertEquals(200, $response->getStatusCode());
}
1) MessagesTest::testPostMessagesResponse
Failed asserting that 500 matches expected 200.
在我的routes.php文件中,我确实注册了此路由:

Route::resource('Messages', 'MessagesController');
在我的MessagesController中,我有所有必需的方法:

<?php namespace App\Http\Controllers;

use App\Http\Requests;
use App\Http\Controllers\Controller;

use Illuminate\Http\Request;

class MessagesController extends Controller {

    /**
     * Display a listing of the resource.
     *
     * @return Response
     */
    public function index()
    {
        return 'response';
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return Response
     */
    public function create()
    {
        return 'response';
    }

    /**
     * Store a newly created resource in storage.
     *
     * @return Response
     */
    public function store()
    {
        return 'response';
    }

    /**
    * Display the specified resource.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        return 'response';
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return Response
     */
    public function edit($id)
    {
        return 'response';
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  int  $id
     * @return Response
     */
    public function update($id)
    {
        return 'response';
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return Response
     */
    public function destroy($id)
    {
        return 'response';
    }

}
这将返回Laravel'Oops!'页因此确实存在某种服务器错误,但我不确定要查找什么。应用程序流看起来不错,但显然不起作用。以下是我的Laravel日志中的堆栈跟踪:

[timestamp] local.ERROR: exception 'Illuminate\Session\TokenMismatchException' in /Users/username/Sites/project/storage/framework/compiled.php:2375
Stack trace:
#0 /Users/username/Sites/project/app/Http/Middleware/VerifyCsrfToken.php(17): Illuminate\Foundation\Http\Middleware\VerifyCsrfToken->handle(Object(Illuminate\Http\Request), Object(Closure))
#1 /Users/username/Sites/project/storage/framework/compiled.php(8858): App\Http\Middleware\VerifyCsrfToken->handle(Object(Illuminate\Http\Request), Object(Closure))
#2 /Users/username/Sites/project/storage/framework/compiled.php(11990): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#3 /Users/username/Sites/project/storage/framework/compiled.php(8858): Illuminate\View\Middleware\ShareErrorsFromSession->handle(Object(Illuminate\Http\Request), Object(Closure))
#4 /Users/username/Sites/project/storage/framework/compiled.php(10696): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#5 /Users/username/Sites/project/storage/framework/compiled.php(8858): Illuminate\Session\Middleware\StartSession->handle(Object(Illuminate\Http\Request), Object(Closure))
#6 /Users/username/Sites/project/storage/framework/compiled.php(11696): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#7 /Users/username/Sites/project/storage/framework/compiled.php(8858): Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse->handle(Object(Illuminate\Http\Request), Object(Closure))
#8 /Users/username/Sites/project/storage/framework/compiled.php(11645): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#9 /Users/username/Sites/project/storage/framework/compiled.php(8858): Illuminate\Cookie\Middleware\EncryptCookies->handle(Object(Illuminate\Http\Request), Object(Closure))
#10 /Users/username/Sites/project/storage/framework/compiled.php(2411): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#11 /Users/username/Sites/project/storage/framework/compiled.php(8858): Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode->handle(Object(Illuminate\Http\Request), Object(Closure))
#12 [internal function]: Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#13 /Users/username/Sites/project/storage/framework/compiled.php(8849): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#14 /Users/username/Sites/project/storage/framework/compiled.php(1862): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#15 /Users/username/Sites/project/storage/framework/compiled.php(1852): Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter(Object(Illuminate\Http\Request))
#16 /Users/username/Sites/project/public/index.php(53): Illuminate\Foundation\Http\Kernel->handle(Object(Illuminate\Http\Request))
#17 /Users/username/Sites/project/server.php(21): require_once('/Users/username/Site...')
#18 {main}
我对Laravel很陌生,所以我不知道如何真正深入研究并使用堆栈跟踪来找出哪里出了问题。我也在没有使用
Route::resource
的情况下尝试过这个方法,但这并不能缓解我的情况。我的GET仍然可以正常工作(
Route::GET
),但是我的POST(
Route::POST
)仍然收到相同的500个错误。包含…/compiled.php中第2375行的函数(来自堆栈跟踪顶部抛出的异常)由Laravel生成,如下所示:

public function handle($request, Closure $next)
{
    if ($this->isReading($request) || $this->tokensMatch($request)) {
        return $this->addCookieToResponse($request, $next($request));
    }
    throw new TokenMismatchException();
}
当时,我试图编辑后测试,因为我实际上没有发送任何数据。这是我修订的第二个测试:

public function testPostMessagesResponse()
{
    $response = $this->call('POST', 'Messages', array("key" => "value"));

    $this->assertEquals(200, $response->getStatusCode());
}

我收到了相同的错误消息。我觉得我错过了一些简单的事情,但我无法理解。非常感谢您的帮助。

您可以在调用堆栈的第一个函数中看到您的问题

#0 /Users/username/Sites/project/app/Http/Middleware/VerifyCsrfToken.php(17): Illuminate\Foundation\Http\Middleware\VerifyCsrfToken->handle(Object(Illuminate\Http\Request), Object(Closure))
看起来Laravel 5正在自动检查CSFR令牌的POST请求(跨站点请求伪造)。这将是您在基于浏览器的UI中在表单上创建的东西,以帮助防止跨站点请求伪造

因为CSFR令牌对于API没有意义,所以您需要删除此中间件。看起来Laravel 5应用程序的默认设置为您提供了一个内核类文件

#File: app/Http/Kernel.php
<?php namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel {

    /**
     * The application's global HTTP middleware stack.
     *
     * @var array
     */
    protected $middleware = [
        'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode',
        'Illuminate\Cookie\Middleware\EncryptCookies',
        'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
        'Illuminate\Session\Middleware\StartSession',
        'Illuminate\View\Middleware\ShareErrorsFromSession',
        'App\Http\Middleware\VerifyCsrfToken',

    ];

    /**
     * The application's route middleware.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => 'App\Http\Middleware\Authenticate',
        'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
        'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
    ];

}    

$middleware
属性输入,您就可以开始了。如果您要创建一个带有浏览器UI的应用程序,那么您需要将此中间件保留在适当的位置,然后仔细阅读,了解如何在特定请求中使用它。(短版本?中间件是一种处理每个HTTP请求的方法,但不会影响实际的应用程序代码)

@Alan Storm:你完全正确。我在Laracasts上发布了同样的问题,你描述的问题也反映在那里。然而,为了在应该得到表扬的地方给予表扬,我收到了一份更优雅的解决方案。所以,我把它贴在这里,如果其他人有同样的问题,他们也会有。来自拉腊卡斯特的JarekTkaczyk:

问题是,Laravel 5为每个请求运行VerifyCsrfToken中间件,没有例外。它验证请求是否已读取(GET、HEAD、OPTIONS)或是否具有有效的令牌

在您的情况下,没有有效的令牌,因此您会得到令牌失配异常。现在,为了解决这个问题,您可以禁用此测试环境验证

只需将app/Http/Middleware/VerifyCsrfToken.php调整为:

现在,快乐测试


我想为这两个问题提供一个替代方案。这两种解决方案都绕过了现有的CSRF中间件(通过禁用或修改其背后的代码)。我认为对于需要CSRF保护和测试的合法情况,修改测试比修改代码以适应非代表性测试更好

我还认为有一个类似这样的代码块

if ('testing' !== app()->environment()) {
   // Path taken only when not testing
} 
正如@beznez自己的代码一样,代码中的任何地方都会要求出现潜在的bug。测试代码应尽可能采用与代码交互的任何其他人/事物相同的路径

在请求中实际拥有CSRF令牌并不是那么困难,就像在正常的浏览器请求中一样。唯一的诀窍是您必须启动一个会话,然后向您的请求添加一个
\u token
键,该键的值由
csrf\u token()
返回(与表单中的值相同)。因此,修改后的测试代码将是:

public function testPostMessagesResponse()
{
    Session::start(); // Start a session        

    $response = $this->call(
        'POST', 'Messages', array("key" => "value", "_token" => csrf_token()));

    $this->assertEquals(200, $response->getStatusCode());
}

本博客对此进行了详细阐述。

与其禁用
VerifyCsrfToken
中间件,不如只模拟真实用户会话而不禁用任何东西

您只需在测试中启动一个新的
会话
,然后在测试受csrf保护的路由时通过
csrfToken

use Session;

...

public function testPostMessagesResponse()
{
    Session::start();
    $params = [
        'key'    => 'value',
        '_token' => csrf_token()
    ];

    $response = $this->call('POST', 'Messages', $params);

    $response->assertSuccessful();
}

YMMV,但一般来说,修改提供中间件的类与修改提供应用程序配置的类相比,似乎不太稳定。(虽然问题似乎是Laravel将中间件类作为
应用程序的一部分
,但没有明确区分什么应该和不应该是用户可编辑的——我的开发传统将CSFR类作为框架的一部分)我刚刚完成了对中间件的回顾,我可以理解将配置(如我在
Kernel.php
中声明的中间件)与Laravel代码库分离是什么意思。现在,我采用了第二种解决方案。霍