PHPUnit: Expected status code 200 but received 419 with Laravel - laravel

I want to test the delete method but I am not getting the expected results from PHPUnit. I receive this message when running the test:
Expected status code 200 but received 419. Failed asserting that false is true.
/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:77
/tests/Unit/CategoriesControllerTest.php:70
Laravel version: 5.5
Thank you for any help!
Controller constructor:
public function __construct()
{
$this->middleware('auth');
$this->middleware('categoryAccess')->except([
'index',
'create'
]);
}
Controller method:
public function destroy($categoryId)
{
Category::destroy($categoryId);
session()->flash('alert-success', 'Category was successfully deleted.');
return redirect()->action('CategoriesController#index');
}
categoryAccess middleware:
public function handle($request, Closure $next)
{
$category = Category::find($request->id);
if (!($category->user_id == Auth::id())) {
abort(404);
}
return $next($request);
}
Category model:
protected $dispatchesEvents = [
'deleted' => CategoryDeleted::class,
];
Event listener
public function handle(ExpensesUpdated $event)
{
$category_id = $event->expense->category_id;
if (Category::find($category_id)) {
$costs = Category::find($category_id)->expense->sum('cost');
$category = Category::find($category_id);
$category->total = $costs;
$category->save();
}
}
PHPUnit delete test:
use RefreshDatabase;
protected $user;
public function setUp()
{
parent::setUp();
$this->user = factory(User::class)->create();
$this->actingAs($this->user);
}
/** #test */
public function user_can_destroy()
{
$category = factory(Category::class)->create([
'user_id' => $this->user->id
]);
$response = $this->delete('/category/' . $category->id);
$response->assertStatus(200);
$response->assertViewIs('category.index');
}

Solution: When you cached your configuration files you can resolve this issue by running php artisan config:clear.
Explanation: The reason why this can resolve the issue is that PHPUnit will use the cached configuration values instead of the variables defined in your testing environment. As a result, the APP_ENV is not set to testing, and the VerifyCsrfTokenMiddleware will throw a TokenMismatchException (Status code 419).
It won't throw this exception when the APP_ENV is set to testing since the handle method of VerifyCsrfTokenMiddleware checks if you are running unit tests with $this->runningUnitTests().
It is recommended not to cache your configuration in your development environment. When you need to cache your configuration in the environment where you are also running unit tests you could clear your cache manually in your TestCase.php:
use Illuminate\Support\Facades\Artisan;
public function createApplication()
{
....
Artisan::call('config:clear')
....
}
Example based on https://github.com/laravel/framework/issues/13374#issuecomment-239600163
Read more about configuration caching in this blog post or the Laravel documentation.

Sometimes in testing you will need to disable middlewares to proceed :
use Illuminate\Foundation\Testing\WithoutMiddleware;
class ClassTest extends TestCase
{
use WithoutMiddleware; // use this trait
//tests here
}
and if you want to disable them just for one specific test use :
$this->withoutMiddleware();

The message here is indeed related to the CSRF middleware. But there is a much better way of attacking this problem than disabling the middleware.
The middleware comes with code built-in that detects if it is being used in a test. This check looks for 2 things:
Am I being ran via a command line
Am I being ran in an environment type of testing
Default, proper setup of the software correctly causes both flags to be true when running PHP unit. However, the most likely culprit is the value in your APP_ENV. Common ways for this to to be incorrect include:
Misconfigured phpunit.xml file. It should contain <server name="APP_ENV" value="testing" />
A shell session that has an explicit value set in APP_ENV that overrides this value
A Docker/docker-compose/kubernetes session that has an explicit value set in APP_ENV. Seeing about getting this value set via the .env and/or phpunit.xml files is perhaps better if possible. Or ensuring the build/test process sets the value.
This one stumped me as well and I was not convinced that I would need the use of WithoutMiddleware since I did not for a different project, but it turned out I had experimented with something on the command line and overrode APP_ENV in bash.

This is the exact solution:
Laravel environment will set after bootstraping application, so you cant change it from appServiceProvider or another source.
for fix this error you need to add this function to App\Http\Middleware\VerifyCsrfToken
public function handle($request, \Closure $next)
{
if(env('APP_ENV') !== 'testing')
{
return parent::handle($request, $next);
}
return $next($request);
}
you need to use env('APP_ENV') that is set in .env.testing file with
APP_ENV=testing

modify the file app/Http/Middleware/VerifyCsrfToken.php by adding:
public function handle($request, \Closure $next)
{
if ('testing' !== app()->environment())
{
return parent::handle($request, $next);
}
return $next($request);
}
source: https://laracasts.com/discuss/channels/testing/trouble-testing-http-verbs-with-phpunit-in-laravel/replies/29878

Related

Change InertiaJS-Laravel default RootView

I am using Laravel 8 and I have installed InertiaJS, but in my directory resources/views/ I have a single file called index.blade.php which I plan to use with InertiaJS.
By default, InertiaJS looks for a file inside that directory called app.blade.php. I know writing the following statement:
\Inertia\Inertia::setRootView('index');
Change the rootView and allow me to use the file I have created. It may seem like a stupid question, but as far as I see it, I can do 2 things ..
Rename file index.blade.php to app.blade.php
Write the previous sentence .. in one of the ServiceProviders that I have
I wonder the following:
InertiaJS-Laravel does not allow publishing a ServiceProvider with the command php artisan vendor:publish? (the output of this command does not show me anything to publish regarding this package)
To solve my problem I should create a ServiceProvider like: php artisan make:provider InertiaServiceProvider and then register it?
Or just add the previous statement to one of the ServiceProvider that already exist? Like in app/Http/Providers/RouteServiceProvider.php
What do you recommend that would be better?
I want to seek the largest possible organization in my project. Thank you very much in advance...
Update; after my initial answer (on 20-09-2020), Inertia introduced middleware to handle your Inertia requests.
As described in the answers below, you can use the command php artisan inertia:middleware to generate this middleware. You can set the root index with:
// Set root template via property
protected $rootView = 'app';
// OR
// Set root template via method
public function rootView(Request $request)
{
return 'app';
}
You can find more info in the docs.
Even tighter, just override the rootView method in App\Http\Middleware\HandleInertiaRequests like this...
public function rootView(Request $request)
{
if ($request->route()->getPrefix() == 'admin') {
return 'layout.admin';
}
return parent::rootView($request);
}
You can do this inside your controller on the fly.
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Inertia\Inertia;
class NewsController extends Controller
{
public function index()
{
Inertia::setRootView('layouts.news');
$users = User::all();
return Inertia::render('News/Index', compact('users'));
}
}
Replace in the App\Http\Middleware\HandleInertiaRequests
protected $rootView = 'app';
with:
public function rootView(Request $request): string
{
if ($request->route()->getPrefix() === '/admin') {
return 'admin.app';
}
return 'app';
}
I think it would be easier to change it in App\Http\Middleware\HandleInertiaRequests.
Be sure to run php artisan inertia:middleware during inertia server-side installation.
Also include it in your web middleware group.
Then go to App\Http\Middleware\HandleInertiaRequests and change the $rootView property to the name of the blade file you want to use. Example:
protected $rootView = 'index';
Extended #Olu Udeh answer
overwrite handle method of App\Http\Middleware\HandleInertiaRequests middleware
public function handle(Request $request, Closure $next)
{
if($request->route()->getPrefix() == 'admin'){
$this->rootView = 'layouts.admin';
}
return parent::handle($request, $next);
}
In laravel 8 this work for me
App\Http\Middleware\HandleInertiaRequests
Code
public function rootView(Request $request)
{
if(request()->is('admin/*') or request()->is('admin'))
{
return 'admin';
}
return parent::rootView($request);
}

Laravel set cookie not working

I have the following code in a custom middleware:
public function handle($request, Closure $next)
{
if($request->hasCookie('uuid')) {
return $next($request);
}
$uuid = 99;
$response = $next($request);
return $response->withCookie(cookie()->forever('uuid', $uuid));
}
I have registered the middleware in the app.php file, but cookies is still not being written. Please can anyone help. Additionally can this above be run as a singleton, so that it is executed once on app start?
Thanks
If you are on a local environment, make sure to set 'secure' => env('SESSION_SECURE_COOKIE', false), around lines #165-170 in your config/session.php file.
On a non-SSL domain (like http://localhost/), this directive must be false. Otherwise cookies will not be set in browser.
Here, I'm mention set and get a cookie in laravel simple example following.
First of the create a controller.
1.php artisan make:controller CookieController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class CookieController extends Controller {
**/* below code a set a cookie in browser */**
public function setCookie(Request $request){
$response = new Response('Hello World');
$response->withCookie(cookie('name', 'Anything else'));
return $response;
}
**/* below code a get a cookie in browser */**
public function getCookie(Request $request){
$value = $request->cookie('name');
echo $value;
}
}
Add a following line code in routes/web.php file (Laravel 5.4)
Route::get('/cookie/set','CookieController#setCookie');
Route::get('/cookie/get','CookieController#getCookie');
And all files add-in project than a run program easily sets and get a cookie.
You can obtain the response object in middleware like so:
public function handle($request, Closure $next)
{
$response = $next($request);
// Do something after the request is handled by the application
return $response;
}
So you could do something like this
if($request->hasCookie('uuid')) {
return $next($request);
}
$uuid = Uuid::generate();
$response = $next($request);
return $response->withCookie(cookie()->forever('uuid', $uuid));
You can use the laravel helper function cookie(). This worked for me.
<?php
//Create:
cookie()->queue(cookie($name, $value, $minutes));
// forever
cookie()->queue(cookie()->forever($name, $value));
//get
request()->cookie($name);
//forget
cookie()->queue(cookie()->forget($name));

Laravel 5.4 Sessions and Auth::user() not available in controller's constructor

I would like to use a User class throught the application. So, I would like to create CustomUser and then inject it into controllers that need it (it would be most of them).
Now, I create an empty instance in serviceprovider. Next, I want to fill it with data that are already saved in Auth::user(). After long time I have not found where to do it.
Auth::user() is empty in middlewares, but is filled with the user data in controllers. I am missing the step where Laravel queries the database and fills Auth:user() with data. I want to avoid making the same query again.
Thanks for any help!
You can use base controller with __get() method. For example:
class Controller
{
public function __get(string $name)
{
if($name === 'user'){
return Auth::user();
}
return null;
}
}
And in the child controllers can call $this->user
Since Laravel 5.3, you do not have access to sessions in controller constructors. This is because the middleware has not been run yet. I know it's difficult to locate, but in the migration documentation from 5.2 > 5.3 (you're probably on 5.4), it shows that the proper way to resolve data from sessions (which auth() is just a wrapper around a session() call to get the user), is to use the following method:
class MyController extends Controller {
protected $user;
public function __construct() {
$this->middleware(function ($request, $next) {
$this->user= auth()->user();
return $next($request);
});
}
}
Then $this->user will reference the auth user to any methods inside of this controller.
Hopefully his helps.
In Laravel 5.6 i used this
$this->middleware(function ($request, $next) {
$id = Auth::user()->id;
$res = $this->validateAnyFunction($id);
if(!$res){
//to redirect to any other route
return $next(redirect()->route("any")->with("failed","Invalid")->send());
}
//this is used to proccess futher funcitons of controller
return $next($request);
});

Laravel how to execute specific function every time a specific controller's functions are used

I am running laravel and i just implemented PHP Excel in order to export excels with data. For security reasons i want to redirect the user if his facebook id is no match with "my administrator facebook id".
My controller is named ExcelController and it has different functions inside it.
I do not want to use the below code inside every function so i am trying to find something to execute like __construct() - everytime this controller is accessed :
if(Auth::user()->facebook_id != env('facebook_id_admin_access')) {
return redirect()->route('home');
}
env('facebook_id_admin_access') is an .env variable(like a constant) with the facebook id i want to give access so it can use the specific controller.
I tried creating a public function __construct(){} in the controller and put the above code block but it doesn't get called.
Is this possible and how? Should i use middleware for that?
EDITED QUESTION
1) Created a middleware CheckAdminAccess
*/
public function handle($request, Closure $next)
{
if(Auth::user()->facebook_id != env('FACEBOOK_ID_ADMIN_ACCESS') || !Auth::check()) {
return redirect()->route('/');
}
return $next($request);
}
2) Updated in app\http\kernel.php to
protected $routeMiddleware = [
'admin_access' => 'App\Http\Middleware\CheckAdminAccess',
];
3) Updated in routes.php :
Route::get('/output/completed', 'ExcelController#completed')->middleware('admin_access');
But it doesn't seem to work, did i forgot anything?
After creating a middleware,
To make it work i changed in my routes.php from :
Route::get('/output/completed', 'ExcelController#completed')->middleware('admin_access');
To :
Route::group(['middleware' => ['auth','admin_access']], function() {
Route::get('/output/completed', 'ExcelController#completed');
});

Disable Laravel 5 (not 5.1) middleware for testing

Is there a way to turn off some Laravel 5.0 middleware for functional tests?
Just edit app/Http/kernel.php file and comment any undesired middleware line in array $middleware.
You don't need to comment the ones in $routeMiddleware since these won't be automatically called, and they need to be specifically activated in the routes.php file.
Another way:
Copy Kernel.php as Kerneltest.php in the same folder.
Then rename the class in Kerneltest.php to Kerneltestand make it extends Kernel.
Then remove any middleware lines from Kerneltest
Then add the following to bootstrap\app.php :
$app->singleton(
'Illuminate\Contracts\Http\Kerneltest',
'App\Http\Kerneltest'
);
Then in public\index.php use
$kernel = $app->make('Illuminate\Contracts\Http\Kerneltest');
or
$kernel = $app->make('Illuminate\Contracts\Http\Kernel');
depending on whether you're testing or not.
I've found simple solution, although probably it may be not "TRUE", but it works. I've modified method handle() in my app/Http/Middleware/VerifyCsrfToken.php to this
public function handle($request, Closure $next)
{
if (env('APP_ENV') === 'testing') {
return $next($request);
}
return parent::handle($request, $next);
}

Resources