2

在开始之前,我想说我已经看过Failed asserting the HTTP status is 200 not 500以及Failed asserting the HTTP status code is 200 not 500。不要让名字欺骗你;他们是2个不同的职位。这两个帖子都超过 9 个月,没有好的答案。

我在测试路线时遇到了一些麻烦。所以,我Laravel 5在我的 Mac Mini 上创建了一个新项目,正在运行OS X Yosemite 10.10.2. 我正在制作一个 API。所以,我首先开始制作 API 的后端部分。我决定我想用它PHPUnit来为这个项目实现 TDD。我的版本PHPUnit4.0.20. 我正在运行PHP 5.5.14,并且正在使用命令行工具Laravel中打包的服务器。artisan

现在,我开始确保我可以连接到服务器。所以,我的第一个测试看起来像:

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

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

这通过就好了。但是,当我转到 POST 请求时,它失败了。这是我的第二个测试的样子:

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

}

所以,目前,不管 HTTP 动词是什么,我都应该得到'response'. 现在,我决定用 curl 验证测试:

curl -X POST http://myapp.com/Messages

这将返回 Laravel '哎呀!页。所以确实存在某种服务器错误,但我不确定要寻找什么。应用程序流程看起来不错,但显然无法正常工作。这是我的 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();
}

那时,我尝试编辑 POST 测试,因为我实际上并没有发送任何数据。所以这是我修改后的第二个测试:

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

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

我收到相同的错误消息。我觉得我错过了一些简单的东西,但我无法弄清楚。任何帮助将不胜感激。

4

4 回答 4

6

@Alan Storm:您对正在发生的事情完全正确。我在 Laracasts 上发布了同样的问题,您所描述的问题也反映在了那里。但是,为了在应得的地方给予信任,我收到了一个更优雅的问题解决方案。所以,我在这里重新发布它,以便其他人有同样的问题。来自 Laracasts 的 JarekTkaczyk:

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

在您的情况下,没有有效的令牌,因此您得到 TokenMismatchException。现在,为了摆脱这个问题,您可以禁用此验证以用于测试环境。

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

public function handle($request, Closure $next)
{
    if ('testing' !== app()->environment())
    {
        return parent::handle($request, $next);
    }

    return $next($request);
}

现在,快乐的测试!

于 2015-02-06T15:27:58.977 回答
5

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

#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 正在自动检查 POST 请求以获取 CSFR 令牌(跨站点请求伪造)。这将是您在基于浏览器的 UI 中的表单上创建的内容,以帮助防止跨站点请求伪造。

由于 A 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',
    ];

}    

对于您的仅 API 项目,您可能只需删除

'App\Http\Middleware\VerifyCsrfToken',

$middlewares物业进入,你会很高兴去的。如果您要创建带有浏览器 UI 的应用程序,您需要保留此中间件,然后阅读中间件以了解如何在特定请求中使用它。(短版?中间件是一种在每个 HTTP 请求上都发生的事情,但不会影响您的实际应用程序代码的方法)

于 2015-02-05T23:56:27.363 回答
4

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

我也认为有一个类似的代码块

if ('testing' !== app()->environment()) {
   // Path taken only when not testing
} 

就像@beznez 自己的回答一样,您代码中的任何地方都在要求偷偷摸摸的错误。尽可能地,测试代码应采用与与代码交互的任何人/任何其他事物相同的路径。

在请求中实际包含 CSRF 令牌并不难,就像它出现在普通浏览器请求中一样。唯一的技巧是您必须开始一个会话,然后_token使用返回的值向您的请求添加一个键csrf_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());
}

此博客文章对此进行了详细说明。

于 2015-06-23T07:56:26.183 回答
0

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

您只需要在测试中开始一个新Session的,然后csrfToken在测试受 csrf 保护的路由时通过。

use Session;

...

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

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

    $response->assertSuccessful();
}
于 2019-01-22T11:06:06.673 回答