How I write Integration Tests for Laravel Socialite powered Apps
This article has been published a while ago.
If this is a technical article some information might be out of date. If something is terribly broken, let me know and I will update the article accordingly.
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.