How to Disable Selected Middleware in Laravel Tests - php

It is common that errors from Authentication and CSRF arise when running phpunit.
In the TestCase we use:
use WithoutMiddleware;
The problem is when forms fail, it usually comes back with a Flash Message and Old Input. We have disabled all middleware so we have no access to Input::old('username'); or the flash message.
Furthermore our tests of this failed form post returns:
Caused by
exception 'RuntimeException' with message 'Session store not set on request.
Is there a way to enable the Session Middleware and disable everything else.

The best way I have found to do this isn't by using the WithoutMiddleware trait but by modifying the middleware you want to disable. For example, if you want to disable the VerifyCsrfToken middleware functionality in your tests you can do the following.
Inside app/Http/Middleware/VerifyCsrfToken.php, add a handle method that checks the APP_ENV for testing.
public function handle($request, Closure $next)
{
if (env('APP_ENV') === 'testing') {
return $next($request);
}
return parent::handle($request, $next);
}
This will override the handle method inside of Illuminate\Foundation\Http\Middleware\VerifyCsrfToken, disabling the functionality entirely.

Laravel >= 5.5
As of Laravel 5.5, the withoutMiddleware() method allows you to specify the middleware to disable, instead of disabling them all. So, instead of modifying all of your middleware to add env checks, you can just do this in your test:
$this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);
Laravel < 5.5
If you're on Laravel < 5.5, you can implement the same functionality by adding the updated method to your base TestCase class to override the functionality from the framework TestCase.
PHP >= 7
If you're on PHP7+, add the following to your TestCase class, and you'll be able to use the same method call mentioned above. This functionality uses an anonymous class, which was introduced in PHP7.
/**
* Disable middleware for the test.
*
* #param string|array|null $middleware
* #return $this
*/
public function withoutMiddleware($middleware = null)
{
if (is_null($middleware)) {
$this->app->instance('middleware.disable', true);
return $this;
}
foreach ((array) $middleware as $abstract) {
$this->app->instance($abstract, new class {
public function handle($request, $next)
{
return $next($request);
}
});
}
return $this;
}
PHP < 7
If you're on PHP < 7, you'll have to create an actual class file, and inject that into the container instead of the anonymous class.
Create this class somewhere:
class FakeMiddleware
{
public function handle($request, $next)
{
return $next($request);
}
}
Override the withoutMiddleware() method in your TestCase and use your FakeMiddleware class:
/**
* Disable middleware for the test.
*
* #param string|array|null $middleware
* #return $this
*/
public function withoutMiddleware($middleware = null)
{
if (is_null($middleware)) {
$this->app->instance('middleware.disable', true);
return $this;
}
foreach ((array) $middleware as $abstract) {
$this->app->instance($abstract, new FakeMiddleware());
}
return $this;
}

You can use trait in test:
use Illuminate\Foundation\Testing\WithoutMiddleware;
Laravel >= 5.7

I might be late, but what I've figured it out:
$this->withoutMiddleware([
'email-verified', //alias does NOT work
EnsureEmailIsVerified::class //Qualified class name DOES WORK
]);

The following worked for me:
use WithoutMiddleware;
public function setUp(): void
{
parent::setUp();
$this->withoutMiddleware();
}

The withoutMiddleware method can only remove route middleware and does not apply to global middleware.
From https://laravel.com/docs/8.x/middleware

Related

Function AFTER authentication and BEFORE view (laravel)

I'm trying to get settings from the database and put them in the config,
my function need the user id so it can bring his settings only,
in the service provider ( boot function ) there is no authentication yet, can you please advise me to the right place to run my function, please note that I need it to run before the view get rendered because there are settings for the layout inside it, this is my function :
// public static becouse it's inside Class//
public static function getAppSettings(){
if (!config('settings') && Auth::check()) {
$user_id = Auth::user()->id;
$settings = AppSettings::where('user_id', $user_id)->get()->all();
$settings = Cache::remember('settings', 60, function () use ($settings) {
// Laravel >= 5.2, use 'lists' instead of 'pluck' for Laravel <= 5.1
return $settings->pluck('value', 'key')->all();
});
config()->set('settings', $settings);
}else{
// this is for testing//
dd('no');
}
}
without the auth, it can work inside the service provider ( boot function ) but it will bring all settings for all the users.
You can create middleware for this.Middleware calls after routes and before controller
php artisan make:middleware Settings
This will create below class
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class Settings
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
// exicute your logic here
return $next($request);
}
}
You can call your method inside handle and before next
You can read more about this in
https://laravel.com/docs/8.x/middleware

Laravel not return the first return

When I extends ApiBaseController in another class, response token denied is doesn't work. even though I put wrong app-token but still give response in another class.
class ApiBaseController extends Controller
{
protected $user;
public function __construct()
{
if (request()->header('app-token') != 'ofdsafalkhguddskjafl01JhBF9mGx2jay'){
return response()->json([
'success'=>false,
'status'=>'401',
'message'=>'Token Denied !',
'response'=>[
'total'=>0,
'data'=>[]
]
]);
}
else{
$this->user = Auth::guard('api')->user();
}
}
}
This class still work even though I put wrong app-token
class AttendeesApiController extends ApiBaseController
{
public function index(Request $request)
{
return Attendee::scope($this->account_id)->paginate($request->get('per_page', 25));
}
}
I want to make sure when app-token is wrong will give Token Denied ! response
please give me some advice
You will have to call parent constructor to make this work.
class AttendeesApiController extends ApiBaseController{
function __construct(){
parent::__construct();
}
public function index(Request $request){
return Attendee::scope($this->account_id)->paginate($request->get('per_page', 25));
}
}
If I am not mistaken, you will also have to put a kind of a die in the constructor to avoid further execution.
Update:
Best way to handle this is to group these routes inside a middleware and have the bearer token check in the middleware itself. This will make your approach more neat and you can easily add new routes that require bearer token check in this route middleware group.
While it is a good idea to keep the token validation concern separated, it is not a good practice to do such thing in the constructor, let alone hide it in the constructor of a base class.
In general, constructors should be used to construct the object, not to "do things".
Because you want to return early, it gets a bit complicated to extract this concern out of the controller. But that's what middleware is for.
Take a look at the Laravel documentation on creating your own middleware (altough what you are trying to do might be already built in)
An example middleware class could look like this:
<?php
namespace App\Http\Middleware;
use Closure;
class CheckToken
{
/**
* Handle an incoming request and check the token.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (...) { //your token check
return ...; // your early-returned json.
}
return $next($request); //otherwise continue
}
}

*SOLVED* PHPUnit in Laravel returns 404 for simple get request, yet works fine for others

In a right pickle with phpunit. I currently have a test class for a resource route with 9 tests in it. All but two of these tests pass, ironically what should be the two simplest; the tests for articles.index and articles.show (the last 2 tests in the code below).
<?php
namespace Tests\Feature;
use App\Article;
use Tests\TestCase;
use App\User;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use DB;
class ArticleTest extends TestCase
{
use RefreshDatabase;
// runs before any of the tests
protected function setUp(): void
{
parent::setUp();
// run tests without language(locale) middleware
$this->withoutMiddleware(\App\Http\Middleware\Language::class);
}
/** #test */
public function unauthenticated_users_can_not_access_create()
{
$this->get('/articles/create')->assertRedirect('/login');
}
/** #test */
public function admin_users_can_access_edit()
{
$article = factory(Article::class)->create();
$record = DB::table('articles')->where('id', $article->id)->first();
$user = factory(User::class)->create();
$user->isAdmin = 1;
$this->actingAs($user);
$this->get('/articles/' . $record->slug . '/edit?locale=en')->assertOK();
}
/** #test */
public function authenticated_but_not_admin_users_can_not_access_create()
{
$this->actingAs(factory(User::class)->create());
$this->get('/articles/create')->assertRedirect('home');
}
/** #test */
public function admin_can_access_create()
{
$user = factory(User::class)->create();
$user->isAdmin = 1;
$this->actingAs($user);
$this->get('/articles/create')->assertOk();
}
/** #test */
public function can_store_an_article()
{
$article = factory(Article::class)->create();
$record = DB::table('articles')->where('id', $article->id)->first();
$this->assertDatabaseHas('articles', ['slug' => $record->slug]);
}
/** #test */
public function admin_can_access_articles_admin_index()
{
$user = factory(User::class)->create();
$user->isAdmin = 1;
$this->actingAs($user);
$this->get('/admin/articles')->assertOk();
}
/** #test */
public function admin_can_delete_article_and_it_is_removed_from_the_database()
{
$user = factory(User::class)->create();
$user->isAdmin = 1;
$this->actingAs($user);
$article = factory(Article::class)->create();
$this->assertDatabaseHas('articles', ['slug' => $article->slug]);
$record = DB::table('articles')->where('id', $article->id)->delete();
$this->assertDatabaseMissing('articles', ['slug' => $article->slug]);
}
/** #test */
public function can_see_article_index_page()
{
$this->get('/articles')->assertOK();
}
/** #test */
public function can_only_access_articles_made_visible()
{
$article = factory(Article::class)->create();
$article->displayStatus = 2;
$this->assertDatabaseHas('articles', ['slug' => $article->slug]);
$this->get('/articles/' . $article->slug)->assertRedirect('/articles');
}
}
The test can_see_article_index_page should return a 200 status code yet it 404's and can_only_access_articles_made_visible should be a redirect status code yet 404's as well.
SEEN HERE
My application is multi-lingual, using the spatie/translatable package and I'm unsure if it's that that is interfering (doesn't really make sense for it to be as the withoutMiddleware line in setUp should prevent this) or something else entirely (i.e my stupidity). My app is built with Laravel 5.8, and I am running phpunit 7.5.
EDIT I found out the error was due to some specific rows not existing in my test database, where in my controller it would fail if it couldn't find them. By adding the line $this->withoutExceptionHandling() to the failing tests it told me this information.
Since all your routes are working but /articles that means that something is not ok with your routes.
You added this route lately: Every time you create a new route or make any changes in the route files of laravel you should run php artisan config:cache command.
I am pretty sure it's not a conflict in routes because of a variable {}, since the status code is a clear 404 meaning that no controller is even accessed that's why i won't elaborate further on possible routing conflicting issues.
If the error persists though get your route at the very top of your routes file and see if it works there. If that's the case that means that one of your routes, overlaps it.
A quick fix to that is to order your static naming routes above the routes using variables for example:
/articles
/{articles}
If you reverse above order your /articles route will never be accessed. This is a case of naming route conflicts.

How do I enable VerifyCsrfToken for some tests?

Laravel by default disables VerifyCsrfToken middleware if running tests. As a result I didn't notice that api routes need csrf token to succeed. Is there a way to enable it back for some tests? Like
function withVerifyCsrfTokenMiddleware()
{
$this->app... // here we're passing something to the app
}
And make amendments in VerifyCsrfToken's handle method. In a custom one. The one, that overrides the method from the framework. But that's just my idea. Any better ones?
Okay, here's what I did:
app/Http/Middleware/VerifyCsrfToken.php:
function handle($request, Closure $next)
{
$dontSkipCsrfVerificationWhenRunningTests
= $this->app->bound('dontSkipCsrfVerificationWhenRunningTests')
&& $this->app->make('dontSkipCsrfVerificationWhenRunningTests');
if (
$this->isReading($request) ||
! $dontSkipCsrfVerificationWhenRunningTests && $this->runningUnitTests() ||
$this->shouldPassThrough($request) ||
$this->tokensMatch($request)
) {
return $this->addCookieToResponse($request, $next($request));
}
throw new TokenMismatchException;
}
tests/TestCase.php:
function withVerifyCsrfTokenMiddleware()
{
$this->app->instance('dontSkipCsrfVerificationWhenRunningTests', TRUE);
}
small change to the answer above, rather than changing the handle method I updated the runningUnitTests method to reduce the chance of an issue on upgrade:
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
];
/**
* Determine if the application is running unit tests.
*
* #return bool
*/
protected function runningUnitTests()
{
$dontSkip
= $this->app->bound('dontSkipCsrfVerificationWhenRunningTests')
&& $this->app->make('dontSkipCsrfVerificationWhenRunningTests');
return $this->app->runningInConsole() && $this->app->runningUnitTests() && !$dontSkip;
}
}

Laravel 5.3 - Sharing $user variable to all views

So after finding out that sharing views in Controller.php's constructer no longer works because it always returns null to Auth::user(), I am looking for a different way to do it.
I am simply looking for a way to pass a $user variable with the current signed in user to all my views.
Previous way which worked in 5.2 and below:
public function __construct()
{
view()->share('signed_in', Auth::check());
view()->share('user', Auth::user());
}
This no longer works. How else can I share variables?
I have tried:
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
$this->signed_in = Auth::guest();
view()->share('signed_in', $this->signed_in);
view()->share('user', $this->user);
return $next($request);
});
}
But the code above does not work. It does load the page without a "Undefined Variable $user" error but it just show the navigation bar and then nothing else. It also messes up the site CSS for some reason.
Is there any other way I can do it?
Please help. Thank you.
I fixed this issue quite easily.
In my \App\Http\Controllers\Controller.php
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
private $user;
private $signed_in;
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
$this->signed_in = Auth::check();
view()->share('signed_in', $this->signed_in);
view()->share('user', $this->user);
return $next($request);
});
}
}
By putting the view()->share() in a closure of a middleware, I was able to achieve this.
Yes, for example, you could create a new service provider, and register it in your config/app.php, and put the share logic in there.
Let's start with creating a new service provider:
~/path/to/project$ php artisan make:provider ShareWithViewServiceProvider
You should see the message Provider created successfully.
Then you should edit your config/app.php, and add the new service provider you've just created:
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\ShareWithViewServiceProvider::class, // <-- This is the new entry
Then create your logic in the boot method of your service provider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ShareWithViewServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
view()->share([
'user' => auth()->user(),
'signedIn' => auth()->check(),
]);
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
}
Please keep in mind that auth()->user() will return null if you are not signed in.
As a shorter solution, you can just put the share logic in the app\Providers\AppServiceProvider.php, which Laravel creates by default, and has nothing in it (it's there for us to use).
Using the middleware callback the way you do is a correct way to go about this, but your assignment is wrong.
Use this instead:
$this->signed_in = Auth::check();

Categories