Get Config in Routes tested? - laravel

I need to make my routes conditional, based on config:
//routes/auth.php
if (config('auth.allow_registration')) {....
The above config param is set in the config file:
//config/auth.php
'allow_registration' => false,
It is all working fine, until I try to unit-test it
public function test_registration_screen_can_be_rendered()
{
config()->set('auth.allow_registration', true);
$response = $this->get('/register');
$response->assertStatus(200);
}
The test case is failing.
I understand that after I change config, I need to reread routes. But how?
I found only this $this->refreshApplication(); it suppose to reread routes, but it also rereads the config.
How can I only reread routes, but keep my modified config intact?

If you change that config parameter's value to be read from an environment variable, then you can override it for a given test class in the setUp() method. When Laravel reads the config, it will first check if there's an environment variable for the value, then fall back on the default if not.
// config/auth.php
'allow_registration' => env('ALLOW_REGISTRATION', false),
// tests/feature/RegistrationEnabledTest.php
namespace Tests\Feature;
use Tests\TestCase;
class RegistrationEnabledTest extends TestCase
{
protected function setUp(): void
{
putenv("ALLOW_REGISTRATION=true");
parent::setup();
}
public function test_registration_screen_can_be_rendered()
{
$response = $this->get('/register');
$response->assertStatus(200);
}
}
If you'd like to set it for all of your tests, you can add it to your phpunit.xml in the php environment section:
<php>
<env name="APP_ENV" value="testing"/>
<env name="ALLOW_REGISTRATION" value="true"/>
</php>
Unfortunately, setting an env only works for either all tests or a given test class, I also couldn't find a way to get it to work for just a single test within a test class ($this->refreshApplication() doesn't seem to respect the env change), and manually changing routes at runtime is not something Laravel is designed for. However, I think it's a reasonable workaround to separate out tests of various config settings into discrete test classes.

You can use a middleware on routes witch you want to have conditions for access. then you can just use the middleware on the route or in your controller. you can learn about it in Laravel good documentation here.

Related

How to test a route in Laravel that uses both `Storage::put()` and `Storage::temporaryUrl()`?

I have a route in Laravel 7 that saves a file to a S3 disk and returns a temporary URL to it. Simplified the code looks like this:
Storage::disk('s3')->put('image.jpg', $file);
return Storage::disk('s3')->temporaryUrl('image.jpg');
I want to write a test for that route. This is normally straightforward with Laravel. I mock the storage with Storage::fake('s3') and assert the file creation with Storage::disk('s3')->assertExists('image.jpg').
The fake storage does not support Storage::temporaryUrl(). If trying to use that method it throws the following error:
This driver does not support creating temporary URLs.
A common work-a-round is to use Laravel's low level mocking API like this:
Storage::shouldReceive('temporaryUrl')
->once()
->andReturn('http://examples.com/a-temporary-url');
This solution is recommended in a LaraCasts thread and a GitHub issue about that limitation of Storage::fake().
Is there any way I can combine that two approaches to test a route that does both?
I would like to avoid reimplementing Storage::fake(). Also, I would like to avoid adding a check into the production code to not call Storage::temporaryUrl() if the environment is testing. The latter one is another work-a-round proposed in the LaraCasts thread already mentioned above.
I had the same problem and came up with the following solution:
$fakeFilesystem = Storage::fake('somediskname');
$proxyMockedFakeFilesystem = Mockery::mock($fakeFilesystem);
$proxyMockedFakeFilesystem->shouldReceive('temporaryUrl')
->andReturn('http://some-signed-url.test');
Storage::set('somediskname', $proxyMockedFakeFilesystem);
Now Storage::disk('somediskname')->temporaryUrl('somefile.png', now()->addMinutes(20)) returns http://some-signed-url.test and I can actually store files in the temporary filesystem that Storage::fake() provides without any further changes.
Re #abenevaut answer above, and the problems experienced in the comments - the call to Storage::disk() also needs mocking - something like:
Storage::fake('s3');
Storage::shouldReceive('disk')
->andReturn(
new class()
{
public function temporaryUrl($path)
{
return 'https://mock-aws.com/' . $path;
}
}
);
$expectedUrl = Storage::disk('s3')->temporaryUrl(
'some-path',
now()->addMinutes(5)
);
$this->assertEquals('https://mock-aws.com/some-path', $expectedUrl);
You can follow this article https://laravel-news.com/testing-file-uploads-with-laravel, and mix it with your needs like follow; Mocks seem cumulative:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
public function testAvatarUpload()
{
$temporaryUrl = 'http://examples.com/a-temporary-url';
Storage::fake('avatars');
/*
* >>> Your Specific Asserts HERE
*/
Storage::shouldReceive('temporaryUrl')
->once()
->andReturn($temporaryUrl);
$response = $this->json('POST', '/avatar', [
'avatar' => UploadedFile::fake()->image('avatar.jpg')
]);
$this->assertContains($response, $temporaryUrl);
// Assert the file was stored...
Storage::disk('avatars')->assertExists('avatar.jpg');
// Assert a file does not exist...
Storage::disk('avatars')->assertMissing('missing.jpg');
}
}
Another exemple for console feature tests:
command : https://github.com/abenevaut/pokemon-friends.com/blob/1.1.3/app/Console/Commands/PushFileToAwsCommand.php
test : https://github.com/abenevaut/pokemon-friends.com/blob/1.1.6/tests/Feature/Console/Files/PushFileToCloudCommandTest.php

Dynamic route url change is not reflecting in laravel package

I am creating a package which gives a config file to customize the route url which it will add, I can see config file values in the controller, but same config('app_settings.url') is coming as null in
pakacge/src/routes/web.php
Route::get(config('app_settings.url'), 'SomeController')
my tests are also giving 404 and app_settings config change is not getting picked by route.
function it_can_change_route_url_by_config() {
// this should be default url
$this->get('settings')
->assertStatus(200);
// change the route url
config()->set('app_settings.url', '/app_settings');
$this->get('app_settings')
->assertStatus(200);
$this->get('settings')
->assertStatus(400);
}
app_setting.php
return [
'url' => 'settings',
'middleware' => []
];
It works when I use this package, but tests fail.
Please help How I can give the option to change the route url from config.
To be honest I think it's impossible to make such test. I've tried using some "hacky" solutions but also failed.
The problem is, when you start such test, all routes are already loaded, so changing value in config doesn't affect current routes.
EDIT
As alternative solution, to make it a bit testable, in config I would use:
<?php
return [
'url' => env('APP_SETTING_URL', 'settings'),
'middleware' => []
];
Then in phpunit.xml you can set:
<env name="APP_SETTING_URL" value="dummy-url"/>
As you see I set here completely dummy url to make sure this custom url will be later used and then test could look like this:
/** #test */
function it_works_fine_with_custom_url()
{
$this->get('dummy-url')
->assertStatus(200);
$this->get('settings')
->assertStatus(404);
}
Probably it doesn't test everything but it's hard to believe that someone would use dummy-url in routing, and using custom env in phpunit.xml give you some sort of confidence only custom url is working fine;

testing in Laravel 5.5 : Class env does not exist

i am starting tests in laravel
i created a ".env.testing" file with this
APP_NAME=myApp
APP_ENV=testing
APP_DEBUG=true
APP_LOG_LEVEL=debug
APP_URL=http://myApp.localhost
DB_CONNECTION=sqlite_testing
i added this in the "connections" part of config/database.php file
...
'sqlite_testing' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
...
in the phpunit.xml file, i added :
<env name="DB_CONNECTION" value="sqlite_testing" />
and i created a UserTest feature :
class UserTest extends TestCase
{
public function setUp()
{
parent::setUp();
Artisan::call('migrate');
Artisan::call('db:seed');
}
public function tearDown()
{
parent::tearDown();
Artisan::call('migrate:reset');
}
public function testCanAccessUserSpace()
{
$user = factory(User::class)->create();
$response = $this->actingAs($user)
->get('/home');
$response->assertStatus(200);
}
}
But when i run the tests, i have this :
ReflectionException: Class env does not exist
What's wrong with my config ?
thanks
I had same error while running the phpunit:
ReflectionException: Class env does not exist
Here was another problem, I've installed package telescope and phpunit tried to load it while testing:
ReflectionException: Class env does not exist
/var/www/html/mat2/vendor/laravel/telescope/src/Telescope.php:263
/var/www/html/mat2/vendor/laravel/telescope/src/Telescope.php:222
and so on.
I've added to phpunit.xml:
<env name="TELESCOPE_ENABLED" value="false"/>
Afterwards, tests are running fine.
So, it may be package testing related error.
If you are using php artisan test --env=testing, add TELESCOPE_ENABLED=false to your .env.testing file and run php artisan config:cache --env=testing.
In my case, set TELESCOPE_ENABLED to false doesn't solve my problem. I have to issue
php artisan config:clear
I use Laravel 7.
Looks like this exception is thrown for a variety of reasons. In your case the issue is with the tearDown function, in specific order of operations within this function. You should call the parent tearDown at the bottom. So this:
public function tearDown()
{
parent::tearDown();
Artisan::call('migrate:reset');
}
Should be refactored as follows:
public function tearDown()
{
Artisan::call('migrate:reset');
parent::tearDown();
}
I suspect the reason is that the app instance gets destroyed inside Illuminate\Foundation\Testing\TestCase. here is the code from Laravel 5.5:
if ($this->app) {
foreach ($this->beforeApplicationDestroyedCallbacks as $callback) {
call_user_func($callback);
}
$this->app->flush();
$this->app = null;
}
This error can be caused by telescope being turned on in a unit testing environment.
I change the default behaviour of telescope to be off and explicitly turn it on in my .env file.
You can change the default to false for telescope by updating your config/telescope.php file.
/*
|--------------------------------------------------------------------------
| Telescope Master Switch
|--------------------------------------------------------------------------
|
| This option may be used to disable all Telescope watchers regardless
| of their individual configuration, which simply provides a single
| and convenient way to enable or disable Telescope data storage.
|
*/
'enabled' => env('TELESCOPE_ENABLED', false), // Change from true to false here.
You will then have to set: TELESCOPE_ENABLED=true in your .env file. This will ensure that you don't have it accidentally turned on in environments you don't want such as production and unit testing.
Might be a bit late for a reply, but the below line in phpunit.xml solved the issue.
<server name="TELESCOPE_ENABLED" value="false"/>
I faced this issue and found issue in config folder > constants file
I was wrote "asset('/')" function in config file, we can't write such functions in config.
Hope this answer will help someone who stuck like this errors.
You need to rename .env.testing to .env, you already set APP_ENV to testing so there is no need to change your .env to .env.testing
EDIT:
Also try to update your dependencies with composer update and try again, it's not a usual bug, so it's something related to your setup of testing environment
I have the same problem in my config/services.php file, my error was calling the route() method in array. I just removed the method and back to works.
For laravel version 8 change app()->environment() to env('ENV_NAME') like
env('SMS_SERVICE_ENDPOINT')
Example:
Previous: app()->environment() === 'production'
New Format should be env('DEV_ENV') === 'production')
Note: Don't forget to add DEV_ENV=production in your .env file
In laravel 8, don't try to use laravel global helper function or Facade after TestCase::tearDown:
class MyTestCase extends TestCase
{
public function tearDown(): void
{
parent::tearDown();
// This will case this exception: Target class [env] does not exist
echo app()->environment();
}
}
Instead, move method BEFORE parent::tearDown, it will work:
public function tearDown(): void
{
echo app()->environment();
parent::tearDown();
}
For posterity, my problem was something else entirely... I was trying to use config or env values in my unit test's "tearDown" method after calling the parent's implementation, which destroyed the application!
So this obviously does not work:
protected function tearDown(): void {
parent::tearDown();
$environment = App::environment();
...
}
but this will...
protected function tearDown(): void {
$environment = App::environment();
...
parent::tearDown();
}

How to do unit testing with Laravel Localization?

I'm using mcamara/laravel-localization package and I can't figure out how to make it work with my unit tests. Both of the following fail with red:
// 1. This one results in "Redirecting to http://myapp.dev/en"
$this->get('/')->assertSee('My App Homepage');
// 2. This one results in 404
$this->get('/en')->assertSee('My App Homepage');
In the browser, http://myapp.dev returns 302 with a redirect to http://myapp.dev/en, fair enough. However, http://myapp.dev/en returns 200. So both cases work 100% fine on the front-end, but not with unit tests.
I do have some customization however, which once again, works like charm in the browser.
// in web.php
Route::group([
'prefix' => app('PREFIX'), // instead of LaravelLocalization::setLocale()
'middleware' => ['localeSessionRedirect', 'localizationRedirect']],
function() {
Route::get('/', function() {
return view('home');
});
}
]);
// in AppServiceProvider.php
public function boot()
{
// This, unlike LaravelLocalization::setLocale(), will determine the
// language based on URL, rather than cookie, session or other
$prefix = request()->segment(1); // expects 'en' or 'fr'
$this->app->singleton('PREFIX', function($app) use ($prefix) {
return in_array($prefix, ['en', 'fr']) ? $prefix : null;
});
}
Hopefully this code makes sense to you. Thanks!
UPDATE
I addressed this problem with the package in a GitHub issue #435.
UPDATE 2
Insofar as I could figure it out, it seems that you can safely test your localized routes as long as you specify the locale in the base URL in your phpunit XML file:
<env name="APP_URL" value="http://myapp.dev/en"/>
However, this would work for your localized GET endpoints (which start with a locale prefix, e.g. 'en'), but not for non-localized POST, PUT, etc. (which don't have any prefix). Hence, you can't really test both kinds of endpoints at the same time, unless you use Dusk (which I don't, as it's an overkill and much slower, almost the same as doing it manually).
I found that if you dump the request URL during testing, it is always http://myapp.dev no matter what endpoint you're accessing. So both LaravelLocalization::setLocale() and my custom app('PREFIX') return null, meaning that not a single route is ever localized during testing. You are screwed either way because if you try to access a route without a locale prefix, you get a 302, but if you do specify the locale, the framework can't find a definition for that route.
One article helped me discover a temporary solution: you need to hideDefaultLocaleInURL to true in laravellocalization.php. This way, the routes matching your default locale won't have any prefix, so you can test them as if they were non-localized.
However, the problem still persists, because how are you supposed to test your application when it is localized? (For ex., when you have language-specific routes that need to be tested). This poses the question whether this package is even compatible with unit testing per se...
The problem
Using mcamara / laravel-localization when I test a show route I get a 404 error.
For instance, testing this route returns me a 404:
Route::get('/posts/{post:slug}', [PostController::class, 'show'])->name('posts.show');
The test:
/** #test */
public function itShouldDisplayThePostsShowViewToGuestUser()
{
$response = $this->get("/posts/{$this->post1->slug}");
$response->assertStatus(200);
$response->assertViewIs('posts.show');
}
The solution
I solved hiding the locale from the URL while testing.
Creating this env variable at the end of phpunit.xml.
...
<env name="LOCALIZATION_HIDE_DEFAULT_LOCALE" value="true"/>
</php>
</phpunit>
And in config/laravellocalization.php setting hideDefaultLocaleInURL like this:
'hideDefaultLocaleInURL' => env('LOCALIZATION_HIDE_DEFAULT_LOCALE', false)
This solution was inspired by this this post:
https://github.com/mcamara/laravel-localization/issues/161#issuecomment-381367191

Where is better to place global variables at laravel?

I have to use global variables in some controllers and views, where is better to place them? At .env file or config/ files for example config/app.php?
Thanks.
global variable is a synonym for bad practice , i think you can't make it better
i also think that this question is opinion based.
anyway
why you dont do this
you can create a new controller lets say BaseController
BaseController extends Controller {
protected $hodor;
public function __construct()
{
$hodor = 'HOLD THE FREAKIN DOOR';
}
}
Then in your other controllers you can do this
MyOtherController extends BaseController {
public function hodor()
{
echo "today: $this->hodor";
}
}
But as i said Global variables are considered as bad practices so i think You should use sessions
i think you can use a config file or .env
Global variables are bad for sure. But using custom config is not the same as using global variables.
Create custom config file and put it under config directory. Then you'll be able to use config's variables from any part of your app:
$variable = config('configname.variablename');

Resources