How to Test Laravel Socialite

laravel
tutorial
socialite
Nabil Hassen
Nabil Hassen
Jul 22, 2025
How to Test Laravel Socialite
Last updated on Jul 22, 2025
Table of contents:

Introduction

Social login is one of the most convenient ways for users to access your application. Laravel makes this simple with Laravel Socialite, a first-party package that abstracts away OAuth complexities.

In this post, we’ll walk through a real-world implementation of a Socialite controller and, more importantly, demystify the tests behind it. Testing third-party authentication might seem tricky, but with mocking and smart assertions, it’s absolutely doable.

Socialite Controller: Overview

Here is the actual SocialiteController class we are testing:

<?php
 
declare(strict_types=1);
 
namespace App\Http\Controllers\Auth;
 
use App\Models\User;
use Illuminate\Auth\Events\Verified;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Laravel\Socialite\Facades\Socialite;
 
class SocialiteController
{
// route: /auth/{provider}/redirect
public function redirect(string $provider): RedirectResponse
{
$this->validate(compact('provider'));
 
return Socialite::driver($provider)->redirect();
}
 
// route: /auth/{provider}/callback
public function handleCallback(string $provider): RedirectResponse
{
$this->validate(compact('provider'));
 
$user = Socialite::driver($provider)->user();
 
try {
$user = User::updateOrCreate([
'provider' => $provider,
'provider_id' => $user->id,
], [
'name' => $user->name ?? $user->nickname ?? 'N/A',
'email' => $user->email,
'password' => Str::random(),
'avatar_url' => $user->avatar,
]);
} catch (UniqueConstraintViolationException $exception) {
throw ValidationException::withMessages([
'email' => 'This email is already taken.',
]);
}
 
if (! $user->hasVerifiedEmail()) {
$user->markEmailAsVerified();
event(new Verified($user));
}
 
Auth::login($user, true);
 
return redirect()->intended(route('dashboard'));
}
 
protected function validate(array $data): void
{
Validator::make($data, [
'provider' => ['required', 'string', 'in:github,google'],
])->validate();
}
}

This controller:

  • Redirects the user to the OAuth provider
  • Handles the callback, updates or creates the user
  • Handles email verification if needed
  • Logs the user in

Now let’s look at how we thoroughly test this logic.

Laravel Socialite Authentication Test Suite

Here is the full test file along with detailed explanations for each case:

<?php
 
declare(strict_types=1);
 
use App\Models\User;
use Illuminate\Auth\Events\Verified;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;
use Laravel\Socialite\Contracts\Provider;
use Laravel\Socialite\Contracts\User as SocialiteUser;
use Laravel\Socialite\Facades\Socialite;
use Mockery\MockInterface;
 
// Test: Redirects to provider
 
test('redirects to auth provider', function () {
$this
->get(route('socialite.redirect', 'github'))
->assertValid()
->assertRedirect();
});
 
// Test: Invalid provider name fails validation
 
test('provider is invalid', function () {
$this
->get(route('socialite.redirect', Str::random()))
->assertInvalid(['provider']);
});
 
// Test: Handles callback and creates a new user
 
test('handles callback from auth provider and creates a user', function () {
$provider = Arr::random(['github', 'google']);
$user = User::factory()->unverified()->make();
 
Event::fake();
 
$socialiteUser = $this->mock(SocialiteUser::class, function (MockInterface $mock) use ($user) {
$mock->id = fake()->randomNumber(3);
$mock->name = $user->name;
$mock->email = $user->email;
$mock->avatar = $user->avatar_url;
});
 
$socialiteProvider = $this->mock(Provider::class, function (MockInterface $mock) use ($socialiteUser) {
$mock->shouldReceive('user')->andReturn($socialiteUser);
});
 
Socialite::shouldReceive('driver')->with($provider)->andReturn($socialiteProvider);
 
$this->assertDatabaseCount('users', 0);
 
$this
->get(route('socialite.callback', $provider))
->assertRedirect(route('dashboard'));
 
Event::assertDispatched(Verified::class);
 
$this
->assertAuthenticated()
->assertDatabaseCount('users', 1)
->assertDatabaseHas('users', [
'provider' => $provider,
'provider_id' => $socialiteUser->id,
'name' => $socialiteUser->name,
'email' => $socialiteUser->email,
'avatar_url' => $socialiteUser->avatar,
])
->assertTrue(User::firstWhere('email', $user->email)->hasVerifiedEmail());
});
 
// Test: Email already exists
 
test('user will not be created if already exists', function () {
$provider = Arr::random(['github', 'google']);
$user = User::factory()->create();
$this->assertDatabaseCount('users', 1);
 
$socialiteUser = $this->mock(SocialiteUser::class, function (MockInterface $mock) use ($user) {
$mock->id = fake()->randomNumber(3);
$mock->name = $user->name;
$mock->email = $user->email;
$mock->avatar = $user->avatar_url;
});
 
$socialiteProvider = $this->mock(Provider::class, function (MockInterface $mock) use ($socialiteUser) {
$mock->shouldReceive('user')->andReturn($socialiteUser);
});
 
Socialite::shouldReceive('driver')->with($provider)->andReturn($socialiteProvider);
 
$this
->get(route('socialite.callback', $provider))
->assertInvalid([
'email' => 'This email is already taken.',
]);
 
$this->assertDatabaseCount('users', 1);
});
 
// Test: Log in user if registered with the same provider
 
test('user will login and will not be created again if already registered with the same provider', function () {
$user = User::factory()->viaSocialite()->create();
$newName = fake()->name();
 
$socialiteUser = $this->mock(SocialiteUser::class, function (MockInterface $mock) use ($user, $newName) {
$mock->id = $user->provider_id;
$mock->name = $newName;
$mock->email = $user->email;
$mock->avatar = $user->avatar_url;
});
 
$socialiteProvider = $this->mock(Provider::class, function (MockInterface $mock) use ($socialiteUser) {
$mock->shouldReceive('user')->andReturn($socialiteUser);
});
 
Socialite::shouldReceive('driver')->with($user->provider)->andReturn($socialiteProvider);
 
$this->assertDatabaseCount('users', 1);
 
$this
->get(route('socialite.callback', $user->provider))
->assertRedirect(route('dashboard'));
 
$this
->assertAuthenticated()
->assertDatabaseCount('users', 1)
->assertDatabaseHas('users', [
'provider' => $user->provider,
'provider_id' => $socialiteUser->id,
'name' => $newName,
'email' => $socialiteUser->email,
'avatar_url' => $socialiteUser->avatar,
]);
});
 
// Test: Same email but different provider
 
test('user will not be created if already registered with another provider', function () {
$user = User::factory()->viaSocialite()->create();
$provider = collect(['google', 'github'])->diff([$user->provider])->random();
$this->assertDatabaseCount('users', 1);
 
$socialiteUser = $this->mock(SocialiteUser::class, function (MockInterface $mock) use ($user) {
$mock->id = fake()->randomNumber(3);
$mock->name = $user->name;
$mock->email = $user->email;
$mock->avatar = $user->avatar_url;
});
 
$socialiteProvider = $this->mock(Provider::class, function (MockInterface $mock) use ($socialiteUser) {
$mock->shouldReceive('user')->andReturn($socialiteUser);
});
 
Socialite::shouldReceive('driver')->with($provider)->andReturn($socialiteProvider);
 
$this
->get(route('socialite.callback', $provider))
->assertInvalid([
'email' => 'This email is already taken.',
]);
 
$this->assertDatabaseCount('users', 1);
});

Each of these tests covers a real-world scenario:

  • ✅ Happy paths (successful creation, successful login)
  • ❌ Failure paths (duplicate email from form or mismatched providers)
  • ✨ User experience improvements (e.g., updated profile info)

Final Thoughts

Socialite makes social logins easy, but without proper testing, you risk account conflicts, missing verification, and hard-to-debug login issues. With a few well-crafted mocks and assertions, you can simulate the entire flow and cover edge cases confidently.

Tips for Testing Socialite:

  • Use Laravel's mocking and facades to simulate third-party services.
  • Validate both positive and negative scenarios.
  • Don’t forget to test for email duplication and provider mismatches.
Nabil Hassen
Nabil Hassen
Full Stack Web Developer

Stay Updated.

I'll you email you as soon as new, fresh content is published.

Thanks for subscribing to my blog.

Latest Posts