Laravel Dusk not picking up APP_URL in .env.dusk.local file - laravel

Laravel dusk does not seem to use the APP_URL set in .env.dusk.local file.
This is my .env.dusk.local file:
APP_URL=http://local.project/
BASE_URL=http://local.project/
APP_ENV=testing
DB_CONNECTION=dusk
This is my login test:
<?php
namespace Tests\Browser;
use Tests\DuskTestCase;
class LoginTest extends DuskTestCase
{
/**
* test_I_can_login_successfully
*
* #return void
*/
public function test_I_can_login_successfully()
{
$this->browse(function ($browser) {
$browser->visit('login')
->keys('#email', 'test#test.co.uk')
->keys('#password', 'test')
->click('.btn-primary')
->assertPathIs('dashboard')
->assertSee('Marco');
});
}
}
But when I run the test instead of going to http://local.project/login goes to http://localhost/public/local
Why is that?

Related

Laravel actingAs guest

Laravel provides a way to authenticate a given user during HTTP testing with
$this->actingAs($user);
Is there a way to unauthenticate that $user within the same test?
Yes, you can unauthenticate using this:
Auth::logout();
https://laravel.com/docs/7.x/authentication#logging-out
Warning: above does far more than just forgetting (acting as if the login did not happen), for example, when using JWT above should invalidate token.
Yes, define new actingAsGuest method in base TestCase class
in file tests/TestCase.php
<?php
namespace Tests;
use Illuminate\Auth\RequestGuard;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
protected function setUp(): void
{
parent::setUp();
// add logout method to RequestGuard
RequestGuard::macro('logout', function() {
$this->user = null;
});
}
// add method to base TestCase class
public function actingAsGuest(): void
{
$this->app['auth']->logout();
}
}
And then in your test class you can use it:
<?php
namespace Tests\Feature;
use App\Models\User;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function test_example()
{
// acting as authenticated user
$this->actingAs(User::factory()->create());
$this->assertAuthenticated();
// acting as unauthenticated user
$this->actingAsGuest();
$this->assertGuest();
}
}
I had same requirements as OP did, but wanted actingAsGuest() to completely reset everything, except Database state.
Full App reset (except DB)
For Laravel 7 (and maybe newer or older)
I toke a look at Laravel's tearDown() and setUp() methods.
And came up with helper method like:
use Illuminate\Support\Facades\Facade;
// ...
function actingAsGuest()
{
// Backup database state.
/** #var \Illuminate\Database\MySqlConnection $connection */
$connection = app('db.connection');
// Reset everything else.
/** #var \Illuminate\Foundation\Application $app */
$app = $this->app;
$app->flush();
$this->app = null;
Facade::clearResolvedInstances();
$this->refreshApplication();
// Restore database state.
app('db')->extend($connection->getName(), function () use ($connection) {
return $connection;
});
}
WARNING !!
Above works fine unless your test's logic caches any of above discarded objects somewhere.
For example, Laravel's DatabaseTransactions trait did cache db facade (in their App-Destroyed-listener).
Which we fixed by overriding said trait's logic.
Like we changed:
// ...
$this->beforeApplicationDestroyed(function () use ($database) {
// ...
Into:
// ...
$this->beforeApplicationDestroyed(function () {
$database = app('db');
// ...

Laravel - How to use faker in PHPUnit test?

It's giving me this error when I run the test:
undefined variable $faker.
This is the WithFaker file.
https://github.com/laravel/framework/blob/5.5/src/Illuminate/Foundation/Testing/WithFaker.php
<?php
namespace Tests\Unit;
use App\User;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class LoginTest extends TestCase
{
use WithFaker;
/**
* A basic test example.
*
* #return void
*/
/** #test */
public function test_example()
{
$user = User::create([
'username' => $faker->firstName(),
]);
}
}
You have to use $this->faker->firstName() not just $faker->firstName()
Update 1
Now when we use WithFaker Trait $this->faker will give us null, to get around this make sure to call $this->setupFaker() first.
e.g.
class SomeFactory
{
use WithFaker;
public function __construct()
{
$this->setUpFaker();
}
}
credit #Ebi
For anyone coming here from 2021. We no longer require the addition of
$this->setUpFaker();
You only need to include the trait as described in the accepted answer.
once you completed installation of Faker.
include autoload file and create instance
$faker = \Faker\Factory::create();
$faker->firstname()
$faker->lastname()
For more information visit
check you seed function run (Faker $faker).

PhpStorm Laravel Dusk w/ a testing database

I currently have PhpStorm running Dusk Test successfully however, I would like it to use the testing database I have set up. Per other threads and resources online, I have created the .env.dusk.local and phpunit.dusk.xml that points to the testing database I have created. When I run the dusk tests in PhpStorm the application that is rendered in Chromium doesn't use the testing database that is described in these files but when I run them using php artisan dusk in the terminal it uses the correct databases.
It seems like I need make phpstorm aware of what env file to use when running the tests. Any clues on how to make this work.
If you're running the tests using artisan dusk, make sure that the APP_ENV setting you are running Dusk in matches the .env.dusk.[environment] setting.
The Dusk browser instance always use the current .env file so...
From the Laravel Dusk docs:
When running tests, Dusk will back-up your .env file and rename your Dusk environment to .env. Once the tests have completed, your .env file will be restored.
If you're not running the artisan dusk command to run your Dusk tests, I suspect that you would have to do something similar to this code before and after running the test suite:
https://github.com/laravel/dusk/blob/2.0/src/Console/DuskCommand.php#L136
If you get this working I'd be very interested in how you did it.
I found this article that works well and describes what the issue is.
https://harings.be/running-laravel-dusk-tests-from-phpstorm-atf2v
tests/DuskTestCase.php
tests/DuskTestCase.php
<?php
namespace Tests;
use Dotenv\Dotenv;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\TestCase as BaseTestCase;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
abstract class DuskTestCase extends BaseTestCase
{
use CreatesApplication;
use DatabaseMigrations;
public static function basePath($path = '')
{
return __DIR__ . '/../' . ($path ? DIRECTORY_SEPARATOR . $path : $path);
}
/**
* Prepare for Dusk test execution.
*
* #beforeClass
* #return void
*/
public static function prepare()
{
static::startChromeDriver();
}
public static function setUpBeforeClass()
{
copy(self::basePath('.env'), self::basePath('.env.backup'));
copy(self::basePath('.env.dusk.local'), self::basePath('.env'));
(new Dotenv(self::basePath()))->overload();
parent::setUpBeforeClass();
}
public static function tearDownAfterClass(): void
{
copy(self::basePath('.env.backup'), self::basePath('.env'));
unlink(self::basePath('.env.backup'));
(new Dotenv(self::basePath()))->overload();
parent::tearDownAfterClass();
}
/**
* Create the RemoteWebDriver instance.
*
* #return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
$options = (new ChromeOptions)->addArguments([
'--disable-gpu',
'--headless',
'--window-size=1920,1080',
]);
return RemoteWebDriver::create(
'http://localhost:9515', DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY, $options
)
);
}
}
You need to add to DuskTestCase.php something like this:
/**
* #beforeClass
* #return void
*/
public static function prepare()
{
//static::startChromeDriver();
copy(base_path('.env'), base_path('.env.backup'));
copy(base_path('.env.dusk.local'), base_path('.env'));
(new Dotenv(base_path()))->overload();
}
/**
* #afterClass
* #return void
*/
public static function finish()
{
copy(base_path('.env.backup'), base_path('.env'));
unlink(base_path('.env.backup'));
(new Dotenv(base_path()))->overload();
}
Thx Andriy, I improved your code, this works for me :
use Dotenv\Dotenv;
public static function basePath($path = '') {
return __DIR__. '/../' . ($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
* Prepare for Dusk test execution.
*
* #beforeClass
* #return void
*/
public static function prepare()
{
copy(DuskTestCase::basePath('.env'), DuskTestCase::basePath('.env.backup'));
copy(DuskTestCase::basePath('.env.dusk.local'), DuskTestCase::basePath('.env'));
(new Dotenv(DuskTestCase::basePath()))->overload();
static::startChromeDriver();
}
public static function closeAll()
{
copy(DuskTestCase::basePath('.env.backup'), DuskTestCase::basePath('.env'));
unlink(DuskTestCase::basePath('.env.backup'));
(new Dotenv(DuskTestCase::basePath()))->overload();
return parent::closeAll();
}
..since base_path() and finish() weren't working in this DuskTestCase class
This worked for me
<?php
namespace Tests;
use Dotenv\Dotenv;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Laravel\Dusk\TestCase as BaseTestCase;
abstract class DuskTestCase extends BaseTestCase
{
use CreatesApplication;
public static function basePath($path = '') {
return __DIR__. '/../' . ($path ? DIRECTORY_SEPARATOR.$path : $path);
}
// [ ... ]
public static function setUpBeforeClass(): void
{
if (file_get_contents(self::basePath('.env')) !== file_get_contents(self::basePath('.env.dusk.local'))) {
copy(self::basePath('.env'), self::basePath('.env.backup'));
}
copy(self::basePath('.env.dusk.local'), self::basePath('.env'));
Dotenv::createMutable(self::basePath())->load();
parent::setUpBeforeClass();
}
public static function tearDownAfterClass(): void
{
copy(self::basePath('.env.backup'), self::basePath('.env'));
unlink(self::basePath('.env.backup'));
Dotenv::createMutable(self::basePath())->load();
parent::tearDownAfterClass();
}
// [ ... ]
}
Found it at https://github.com/laravel/dusk/issues/883

Laravel 5.2 - ServiceProvider boot not working

Trying to use service provider to set a variables throughout all views pages that #extends('layouts.app') but not working, first I show the codes below.
AppServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Auth;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
// Using class based composers...
view()->composer('layouts.app', function($view){
$view->with('current_user', Auth::user());
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
When I visit the page that calls {{$current_user}}, it shows the following error
ErrorException in 5a5612347179ad88a6d4ebacc5d911a184c1b4ed.php line 14:
Undefined variable: current_user (View: C:\xampp\htdocs\soyegg\resources\views\shops\edit\showroom.blade.php)
Went through a several questions solved in the website and checked the followings but still failed:
1. AppServiceProvider is in config.app (default by Laravel 5.2)
2. php artisan clear-compiled
3. php artisan optimize
4. check whether there is compiled.php and try to clear it mannually but there is in neither storage/framework nor vendor.
Please help!
I think your ServiceProvider is working fine, you only need to change the way you passing data to the view. I've tested it myself and I can confirm that passing data to a view which later will be included with #extends() won't work. Instead you can use another methods:
view()->share('current_user', Auth::user());
Or using wildcard:
view()->composer('*', function ($view) {
$view->with('current_user', Auth::user());
});
// wildcard with prefix
view()->composer('prefix.*', function ($view) {
$view->with('current_user', Auth::user());
});

Laravel Seeder for Different Environments

I created two folders in my seeder folder:
/seeds
/local
/production
DatabaseSeeder.php
Then, defined the following inside DatabaseSeeder.php
class DatabaseSeeder extends Seeder {
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Eloquent::unguard();
// Load production seeder
if (App::Environment() === 'production')
{
$this->call('production/UsersTableSeeder');
}
// Load local seeder
if (App::Environment() === 'local')
{
$this->call('local/UsersTableSeeder');
}
}
}
Now I know I can't do call('local/UsersTablderSeeder'), and that is my question. How can I call() the seeder files from their respective folders?
Edit
To be clear, when I run the code as it is shown above, I get the following error
[ReflectionException]
Class local/UsersTableSeeder does not exist
I just tried this quickly and got it working, so I'll show you how I set it up and hopefully that helps.
app/database/seeds/local/UsersTableSeeder.php
<?php namespace Seeds\Local;
use Illuminate\Database\Seeder as Seeder;
Class UsersTableSeeder extends Seeder {
public function run () {
dd('local');
}
}
app/database/seeds/production/UsersTableSeeder.php
<?php namespace Seeds\Production;
use Illuminate\Database\Seeder as Seeder;
Class UsersTableSeeder extends Seeder {
public function run () {
dd('production');
}
}
app/database/seeds/DatabaseSeeder.php
<?php
class DatabaseSeeder extends Seeder {
/**
* Run the database seeds.
*
* #return void
*/
public function run(){
Eloquent::unguard();
// Load production seeder
if (App::Environment() === 'production')
{
$this->call('Seeds\Production\UsersTableSeeder');
}
// Load local seeder
if (App::Environment() === 'local')
{
$this->call('Seeds\Local\UsersTableSeeder');
}
}
}
And don't forget to run composer dump-autoload.
Hope that helps.
Laravel 5.7 or higher
if ( App::environment('local') ) {
$this->call(Seeder::class);
}
if ( App::environment('production') ) {
$this->call(Seeder::class);
}
if ( App::environment('testing') ) {
$this->call(Seeder::class);
}
The problem is '/'. You should use '\' instead.

Resources