Прежде чем начать, я хотел бы сказать, что я уже посмотрел на Не удалось подтвердить статус HTTP 200, а не 500 так же как Не удалось установить код состояния HTTP 200, а не 500. Не позволяйте именам обмануть вас; это 2 разных поста. Оба этих поста старше 9 месяцев и не имеют хороших ответов.
У меня проблемы с тестированием маршрутов. Итак, я сделал новый Laravel 5
проект на моем Mac Mini работает OS X Yosemite 10.10.2
, Я делаю API. Итак, я начал делать бэкэнд-часть API первым. Я решил, что хочу использовать PHPUnit
реализовать TDD для этого проекта. Моя версия PHPUnit
является 4.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.
В моем файле rout.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
), но мой пост (Route::post
) все еще получает ту же самую ошибку 500. Функция, охватывающая строку 2375 в … / compiled.php (из исключения, сгенерированного в верхней части трассировки стека), генерируется 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());
}
Я получаю то же сообщение об ошибке. Я чувствую, что упускаю что-то простое, но я не могу понять это. Любая помощь будет принята с благодарностью.
Вы можете увидеть свою проблему в первой функции стека вызовов
#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 (подделка межсайтового запроса). Это будет то, что вы создадите в своей форме в браузерном интерфейсе, чтобы предотвратить подделку межсайтовых запросов.
Так как токен CSFR не имеет смысла для API, вам нужно удалить это промежуточное ПО. Кажется, что настройка по умолчанию для приложения Laravel 5 дает вам файл класса Kernal
#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
собственности, и вы будете хорошо идти. Если вы собираетесь создать приложение с пользовательским интерфейсом браузера, вам нужно сохранить это промежуточное ПО, а затем прочитать на Middlewares чтобы увидеть, как вы можете использовать его по конкретным запросам. (Короткая версия? Промежуточные программы — это способ выполнять действия, которые происходят с каждым HTTP-запросом, но не влияют на реальный код приложения)
@Alan Storm: вы абсолютно правы в происходящем. Я разместил этот же вопрос на Laracasts, и проблема, которую вы описываете, отражена там. Тем не менее, чтобы отдать должное, когда это необходимо, я получил более элегантное решение проблемы. Итак, я публикую это здесь, чтобы у других была такая же проблема. От JarekTkaczyk на Laracasts:
Дело в том, что 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);
}
Теперь счастливого тестирования!
Я хотел бы предложить альтернативное решение для двух в настоящее время здесь. Оба решения введение в заблуждение промежуточное программное обеспечение 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());
}
Это подробно описано в этом блоге сообщение.
Вместо того, чтобы отключить 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();
}