How I write Integration Tests for Laravel Socialite powered Apps

• 3 min read

I recently began working on screeenly again and wanted to share a neat trick how you could write integration tests for your Laravel Socialite integration.

Let's assume you have successfully installed laravel/socialite in your project and everything is set up to work with the GitHub OAuth API. You would have the following controller somewhere in your application.

namespace App\Http\Controllers\App\OAuth;

use App\Http\Controllers\Controller;
use App\Models\User;
use Laravel\Socialite\Contracts\Factory as Socialite;

class GithubController extends Controller
{
    protected $socialite;

    protected $user;

    public function __construct(Socialite $socialite, User $user)
    {
        $this->socialite = $socialite;
        $this->user = $user;
    }

    /**
     * Redirect User to GitHub to approve OAuth Handshake.
     * @return Redirect
     */
    public function redirect()
    {
        return $this->socialite->driver('github')->scopes(['user:email'])->redirect();
    }

    /**
     * Handle Return Request from GitHub OAuth API
     * @return Redirect
     */
    public function handle()
    {
        $oAuthUser = $this->socialite->driver('github')->user();

        // Just Pseudo Code
        if (! $this->user->exists($oAuthUser) {
            $user = $this->user->create($oAuthUser);
        }
        else {
            $user = $this->user->getByOAuthInformation($oAuthUser);
        }

        auth()->login($user);

        return redirect()->route('home');
    }
}

The corresponding routes would be oauth/github/redirect and oauth/github/handle.

In my test I added the following method to mock Socialite:

use Laravel\Socialite\Contracts\Factory as Socialite;

/**
 * Mock the Socialite Factory, so we can hijack the OAuth Request.
 * @param  string  $email
 * @param  string  $token
 * @param  int $id
 * @return void
 */
public function mockSocialiteFacade($email = 'foo@bar.com', $token = 'foo', $id = 1)
{
    $socialiteUser = $this->createMock(Laravel\Socialite\Two\User::class);
    $socialiteUser->token = $token;
    $socialiteUser->id = $id;
    $socialiteUser->email = $email;

    $provider = $this->createMock(Laravel\Socialite\Two\GithubProvider::class);
    $provider->expects($this->any())
        ->method('user')
        ->willReturn($socialiteUser);

    $stub = $this->createMock(Socialite::class);
    $stub->expects($this->any())
        ->method('driver')
        ->willReturn($provider);

    // Replace Socialite Instance with our mock
    $this->app->instance(Socialite::class, $stub);
}

First we create a mock of Laravel\Socialite\Two\User. This is the user object Socialite returns for OAuth2 calls and you will get when you execute the following code in your handle method in your controller:

$user = $this->socialite->driver('github')->user();

(I also added three arguments to the method which will then be attached to the mocked user object. This makes testing different scenarios much easier.)

Next, we create a mock of Laravel\Socialite\Two\GithubProvider. This instance will return our mocked User when we call the method user on it.

We also create a mock of Socialite which is an alias for the contract Laravel\Socialite\Contracts\Factory. This mock will return our mocked GithubProvider when the method driver is called. (Do you see how all these 3 mocks stick together?)

Next, we swap the Socialite implementation in our application with our mock. We use Laravel's Binding Feature for this.

Let's get started with our tests. First, we want to test that our users get redirected to the correct URL. Here's the test to do that:

/** @test */
public function it_redirects_to_github()
{
    $response = $this->call('GET', '/oauth/github/redirect');

    $this->assertContains('github.com/login/oauth', $response->getTargetUrl());
}

The $response variable is an instance of Symfony\Component\HttpFoundation\RedirectResponse and has a convenient getTargetUrl() method on it. (I don't test the entire URL. I just want to be sure the user gets redirected to "github.com" and not "foo.com")

Next, we need to test the response which we receive from GitHub: When a user returns from the GitHub OAuth screen we should read the user information, create a new user, log the user in and redirect to our home route.

The tests would look like this:

/** @test */
public function it_retrieves_github_request_and_creates_a_new_user()
{
    // Mock the Facade and return a User Object with the email 'foo@bar.com'
    $this->mockSocialiteFacade('foo@bar.com');

    $this->visit('/oauth/github/handle')
        ->seePageIs('/home');

    $this->seeInDatabase('users', [
        'email' => 'foo@bar.com',
    ]);
}

Pretty easy, right? I'm sure there would be better ways to do this. If you have a better solution to this problem let me know.