The title is a bit confusing, I know.
I have a simple polymorphic "following" system for the User::class, where Users follow other Users or some other models.
I wrote tests for the follow unfollow actions and everything runs fine.
I have a problem testing an attribute that is set in a global scope.
The global scope does the following.
In order to avoid the N+1 issue when rendering an index page of users, where every user has a follow/unfollow button. In order to check if the current user has followed the users in the list, I used a subselect query with addSelect() and placed that in a GlobalScope on the User model
class User extends Authenticatable
{
// stuff
protected static function booted()
{
// I need the authenticated user here
$user = auth()->user(); // is this stupid? is there a better way?
static::addGlobalScope('current_user_follower', function(Builder $builder) use ($user) {
$builder->addSelect([
'current_user_follower' => Follow::
select('user_id')
->whereColumn('followables.followable_id', 'users.id')
->where('followables.followable_type', User::class)
->where('followables.user_id', ($user) ? $user->id : 0) // there will never be a user with ID of 0
->limit(1)
]);
});
}
// there's also this method because the scope returns Null if user isnt a current follower or not logged in
public function getCurrentUserFollowerAttribute($value): bool
{
return (is_null($value)) ? false : true;
}
}
The above actually works when I request an index page of all users, and if a user is already followed the button displays "unfollow". All in a single query thus avoiding the N+1 problem.
I had to write out the idea before I wrote the unit test that is checking the attribute set by that global scope.
Now I set out to cover it in an actual test but when I request a user list the auth()->user() is awlays null, even tho I tried $this->actingAs(), $this->be(), or Auth::guard()->setUser().
/** @test */
public function user_model_have_initial_follow_condition()
{
$currentUser = $this->createUser();
$followableUsers = User::factory()->count(100)->create();
$this->actingAs($currentUser->fresh());
$followableUsers
->random(25)
->each(fn($followableUser) => (new FollowUserAction)->execute($currentUser, $followableUser));
$this->assertCount(25, $currentUser->followsUsers); // this is OK
// this is not passing, also I know it isnt asserting anything and it isnt an actual test
User::
where('id', '!=', $currentUser->id)
->each(function($user) {
dump($user->current_user_follower); // every user reports false and I dont get the 25 true where I set the follow relationshpis
});
}
When I dd() the auth()->user() inside the global scope it returns null, so my best guess is that requesting the User list in the test is done "outside" the normal flow of a an actual request. Is there a way to "encapsulate" if that's the correct terminology, the User query inside the test so that the authenticated user is set in the global scope??