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 = '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.
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 '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.