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');
// ...
Related
I have a model that has a relationship with a View, that is complicate to popolate for make the feature test, but in the same time this is called from some component that are inside the controller called.
The following code is an example:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\TemperatureView;
class Town extends Model
{
function temperature()
{
return $this->hasOne(TemperatureView::class);
}
}
This is an example of the controller:
<?php
namespace App\Http\Controllers;
use App\Models\Town;
class TownController extends Controller
{
public function update($id)
{
// Here is the validation and update of Town model
$UpdatedTown = Town::where('id',$id);
$UpdatedTown->update($data);
$this->someOperation($UpdatedTown);
}
private function someOperation($Town)
{
//Here there is some operation that use the temperature Relationship
/*
Example:
$Town->temperature->value;
*/
}
}
The test is like is like this:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
use App\Models\TownModel;
use Mockery;
use Mockery\MockInterface;
class TownTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function test_get_town_temperature()
{
$payload = ['someTownInformation' => 'Value'];
$response = $this->post('/Town/'.$idTown,$payload);
$response->assertStatus(200);
//This test failed
}
public function test_get_town_temperature_with_mocking()
{
$this->instance(
TownModel::class,
Mockery::mock(TownModel::class, function (MockInterface $mock) {
$MockDataTemperature = (object) array('value'=>2);
$mock->shouldReceive('temperature')->andReturn($MockDataTemperature);
})
);
$payload = ['someTownInformation' => 'Value'];
$response = $this->post('/Town/'.$idTown,$payload);
$response->assertStatus(200);
//This test also failed
}
}
The first test failed because the Controller has some check on the relationship temperature, that is empty because the view on database is empty.
The second test failed also for the same reason. I tried to follow some others questions with the official guide of Laravel Mocking. I know this is mocking object and not specially Eloquent.
Is something I'm not setting well?
If it's not possible to mock only the function, is possible to mock all the relationship of view, bypassing the DB access to that?
Edit
I undestand that the mocking work only when the class is injected from laravel, so what I wrote above it's not pratical.
I don't know if it's possible to mock only it, I saw a different option, that to create the interface of the model and change for the test, but I didn't want to make it.
I am following a tutorial to create a referal system in Laravel. In the tutorial it was not shown how to implement the addCredit() method of the user model class. I am a bit confuse. Assuming I have another table to keep the record of credits like :
user_credits
------------
user_id
credits
Is it good practice to write the code on user model's addCredits method to update the user_credits table? What will be the best in this case?
class User extends Authenticatable
{
/**
* Add bonus to the user
*/
public function addCredits($credit) {
//
}
}
The listener class to handle addition of the bonus for both the users.
namespace App\Listeners;
use App\Events\UserReferred;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class RewardUser
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param UserReferred $event
* #return void
*/
public function handle(UserReferred $event)
{
$referral = \App\ReferralLink::find($event->referralId);
if (!is_null($referral)) {
\App\ReferralRelationship::create(['referral_link_id' => $referral->id, 'user_id' => $event->user->id]);
if ($referral->program->name === 'Sign-up Bonus') {
// User who was sharing link
$provider = $referral->user;
// add credits to provider
$provider->addCredits(15);
// User who used the link
$user = $event->user;
$user->addCredits(20);
}
}
}
}
I'm not pretty sure, is it good practice or not, but i prefer abstract such things into a standalone service.
In your case it would be something like that:
CreditService
namespace App\Services;
use App\User;
class CreditService
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function addCredits($credits)
{
$this->user->credits += $credits;
$this->user->save();
}
}
Then in controller/listener you can work with this service
use App\Services\CreditService;
...
public function handle(UserReferred $event)
{
$referral = \App\ReferralLink::find($event->referralId);
if ( !is_null($referral) ) {
\App\ReferralRelationship::create([
'referral_link_id' => $referral->id,
'user_id' => $event->user->id,
]);
if ( $referral->program->name === 'Sign-up Bonus' ) {
(new CreditService($referral->user))->addCredits(15);
(new CreditService($event->user))->addCredits(20);
}
}
}
The way how you make and then use service might be different. So, if you don't want work via constructors, you can write static class and pass User into method directly.
I often put some additional actions into services. For example, fire events when i need to do it. Or log some things.
In laravel we can use with() along with redirect(), like
return redirect('home')->with(['message' => 'Some message');
I want to create some other functions like withError(), withSuccess().
How and where to create this ?
As the Laravel RedirectResponse class uses the Macroable trait, you can register response macros to do this.
Just create a new service provider say ResponseMacroServiceProvider. Register it in your app.php and register a macro in the boot method like so:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Http\RedirectResponse;
class ResponseMacroServiceProvider extends ServiceProvider
{
/**
* Register the application's response macros.
*
* #return void
*/
public function boot()
{
RedirectResponse::macro('withError', function ($value) {
return; // add logic here
});
RedirectResponse::macro('withSuccess', function ($value) {
return; // add logic here
});
}
}
I have created trait as follows on this page app/Traits/ModelEventThrower.php
namespace App\Traits;
use Input;
use Event;
use App\Events\ActivityLog;
use Illuminate\Database\Eloquent\Model;
//use Illuminate\Support\Facades\Event;
/**
* Class ModelEventThrower
* #package App\Traits
*
* Automatically throw Add, Update, Delete events of Model.
*/
trait ModelEventThrower {
/**
* Automatically boot with Model, and register Events handler.
*/
protected static function bootModelEventThrower()
{
foreach (static::getModelEvents() as $eventName) {
static::$eventName(function (Model $model) use ($eventName) {
try {
$reflect = new \ReflectionClass($model);
echo "here";exit;
} catch (\Exception $e) {
return true;
}
});
}
}
/**
* Set the default events to be recorded if the $recordEvents
* property does not exist on the model.
*
* #return array
*/
protected static function getModelEvents()
{
if (isset(static::$recordEvents)) {
return static::$recordEvents;
}
return [
'created',
'updated',
'deleted',
];
}
}
My City Model is something like this
namespace App;
use App\Traits\ModelEventThrower;
use App\Events\ActivityLog;
use Illuminate\Database\Eloquent\Model;
use Event;
class City extends Model
{
use ModelEventThrower;
//protected static $recordEvents = ['updated'];
...
}
My CitiesController is
namespace App\Http\Controllers\Admin;
use App\City;
use App\Country;
use Input;
use Validator;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class CitiesController extends Controller
{
......
public function update(City $city,Request $request)
{
......
$city->where('id','=',$input['id'])->update($input);
Somehow, I dont see its calling the function written in trait file. When I tried to create $city->create($input); it echos "here" and stops execusion, but not doing same for update function , however I could successfully update the records.
Any suggestion/help will be highly appreciated.
I had a similar issue with Laravel. By adding a constructor in the model to call the boot() function of the parent Model, like so:
public function __construct()
{
parent::boot();
}
you can make sure that all the traits are booted. This solved it for me.
whenever i tried to use \Auth::User() i am getting non object property because my Auth::guest() returns true whenever i use them in service provider
use Illuminate\Contracts\View\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\View\Factory;
use App\relations;
use App\User;
use DB;
use Illuminate\Support\Facades\Auth;
class RelationServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
\Auth::User()->id;
$relation_friend_for_logged_user = DB::select( DB::raw("SELECT * FROM users"));
$value = "asd";
// for injecting objct
View()->share('count', $value);
}
but why \Auth::guest() is returning true whether i am logged in
You probably want to use a View Composer for this. As far as I know the authenticated user is not yet available in your service providers boot method.
public function boot(Guard $auth) {
view()->composer('*', function($view) use ($auth) {
// get the current user
$currentUser = $auth->user();
// do stuff with the current user
// ...
// pass the data to the view
$view->with('currentUser', $currentUser);
});
}
Code modified from https://laracasts.com/discuss/channels/general-discussion/how-do-i-get-the-current-authenticated-user-laravel-5