How I write Integration Tests for Laravel Socialite powered Apps

• 4 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.

Example Application Code #

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.

Preparation #

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 = '[email protected]', $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.

Writing Tests #

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 '[email protected]'
    $this->mockSocialiteFacade('[email protected]');

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

    $this->seeInDatabase('users', [
        'email' => '[email protected]',
    ]);
}

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.