Úvod
Přihlášení pomocí sociálních sítí je jedním z nejpohodlnějších způsobů, jak mohou uživatelé přistupovat k vaší aplikaci. Laravel to usnadňuje pomocí Laravel Socialite, balíček první strany, který abstrahuje od složitostí protokolu OAuth.
V tomto příspěvku si ukážeme skutečnou implementaci řadiče Socialite a hlavně, demystifikovat testy za ním. Testování ověřování třetí stranou se může zdát složité, ale s pomocí mockingu a chytrých tvrzení je naprosto proveditelné.
Společenský kontrolor: Přehled
Zde je aktuální SocialiteController třídy, kterou testujeme:
<?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(); }}
Tento ovladač:
- Přesměruje uživatele na zprostředkovatele OAuth.
- Zpracovává zpětné volání, aktualizuje nebo vytváří uživatele
- V případě potřeby zpracovává ověření e-mailu
- Přihlášení uživatele
Nyní se podívejme, jak tuto logiku důkladně otestovat.
Sada testů ověřování Laravel Socialite
Zde je celý testovací soubor s podrobným vysvětlením pro každý případ:
<?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);});
Každý z těchto testů zahrnuje reálný scénář:
- ✅ Šťastné cesty (úspěšné vytvoření, úspěšné přihlášení)
- ❌ Cesty selhání (duplicitní e-mail z formuláře nebo neshodní poskytovatelé)
- ✨ Vylepšení uživatelského prostředí (např. aktualizované informace o profilu)
Závěrečné myšlenky
Socialite usnadňuje přihlašování do sociálních sítí, ale bez řádného testování hrozí konflikty účtů, chybějící ověření a obtížně odstranitelné problémy s přihlašováním. Pomocí několika dobře vytvořených maket a tvrzení můžete simulovat celý tok a spolehlivě pokrýt okrajové případy.
Tipy pro testování Socialite:
- Pomocí funkce Laravel mocking a fasád můžete simulovat služby třetích stran.
- Ověřte pozitivní i negativní scénáře.
- Nezapomeňte otestovat duplicitu e-mailů a neshody poskytovatelů.