Skip to content

Commit 1d19358

Browse files
Add Testing Fakes (#756)
* Add initial implementation * WIP * Add tests * Tweak test comment * Remove unused imports * Spacing * PHP <= 8.0 compatibility * Another PHP compatibility tweak * Update SocialiteFakeTest.php * Remove redirect param and fix compatibility * Remove Fake interface since it doesn't exist in earlier Laravel versions * Remove unused property * formatting * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 193219f commit 1d19358

File tree

4 files changed

+318
-0
lines changed

4 files changed

+318
-0
lines changed

src/Socialite.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Support\Facades\Facade;
66
use Laravel\Socialite\Contracts\Factory;
7+
use Laravel\Socialite\Testing\SocialiteFake;
78

89
/**
910
* @method static \Laravel\Socialite\Contracts\Provider driver(string $driver = null)
@@ -27,4 +28,26 @@ protected static function getFacadeAccessor()
2728
{
2829
return Factory::class;
2930
}
31+
32+
/**
33+
* Register a fake Socialite instance.
34+
*
35+
* @param string $driver
36+
* @param \Laravel\Socialite\Contracts\User|\Closure|array|null $user
37+
* @return \Laravel\Socialite\Testing\SocialiteFake
38+
*/
39+
public static function fake(string $driver, $user = null)
40+
{
41+
$root = static::getFacadeRoot();
42+
43+
if ($root instanceof SocialiteFake) {
44+
$fake = $root;
45+
} else {
46+
$fake = new SocialiteFake($root);
47+
48+
static::swap($fake);
49+
}
50+
51+
return $fake->fake($driver, $user);
52+
}
3053
}

src/Testing/FakeProvider.php

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
namespace Laravel\Socialite\Testing;
4+
5+
use Closure;
6+
use Illuminate\Http\RedirectResponse;
7+
use Illuminate\Support\Traits\ForwardsCalls;
8+
use Laravel\Socialite\Contracts\Provider;
9+
10+
class FakeProvider implements Provider
11+
{
12+
use ForwardsCalls;
13+
14+
/**
15+
* The driver name.
16+
*
17+
* @var string
18+
*/
19+
protected $driver;
20+
21+
/**
22+
* The provider resolver.
23+
*
24+
* @var \Closure
25+
*/
26+
protected $resolver;
27+
28+
/**
29+
* The original provider instance.
30+
*
31+
* @var \Laravel\Socialite\Contracts\Provider
32+
*/
33+
protected $provider;
34+
35+
/**
36+
* The fake user to return.
37+
*
38+
* @var \Laravel\Socialite\Contracts\User|\Closure|array|null
39+
*/
40+
protected $user = null;
41+
42+
/**
43+
* Create a new fake provider instance.
44+
*
45+
* @param string $driver
46+
* @param \Closure $resolver
47+
* @param \Laravel\Socialite\Contracts\User|\Closure|array|null $user
48+
*/
49+
public function __construct($driver, $resolver, $user = null)
50+
{
51+
$this->driver = $driver;
52+
$this->resolver = $resolver;
53+
$this->user = $user;
54+
}
55+
56+
/**
57+
* Redirect the user to the authentication page for the provider.
58+
*
59+
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Illuminate\Http\RedirectResponse
60+
*/
61+
public function redirect()
62+
{
63+
return new RedirectResponse('https://socialite.fake/'.$this->driver.'/authorize');
64+
}
65+
66+
/**
67+
* Get the User instance for the authenticated user.
68+
*
69+
* @return \Laravel\Socialite\Contracts\User
70+
*/
71+
public function user()
72+
{
73+
if ($this->user instanceof Closure) {
74+
return ($this->user)();
75+
}
76+
77+
return $this->user ?? $this->provider()->user();
78+
}
79+
80+
/**
81+
* Get the original provider instance.
82+
*
83+
* @return \Laravel\Socialite\Contracts\Provider
84+
*/
85+
public function provider()
86+
{
87+
if (isset($this->provider)) {
88+
return $this->provider;
89+
}
90+
91+
return $this->provider = ($this->resolver)();
92+
}
93+
94+
/**
95+
* Handle calls to methods that are not available on the fake provider.
96+
*
97+
* @param string $method
98+
*/
99+
public function __call($method, array $parameters)
100+
{
101+
return $this->forwardCallTo($this->provider(), $method, $parameters);
102+
}
103+
}

src/Testing/SocialiteFake.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
namespace Laravel\Socialite\Testing;
4+
5+
use Laravel\Socialite\Contracts\Factory;
6+
7+
class SocialiteFake implements Factory
8+
{
9+
/**
10+
* The original factory instance.
11+
*
12+
* @var \Laravel\Socialite\Contracts\Factory
13+
*/
14+
protected $factory;
15+
16+
/**
17+
* The fake provider instances.
18+
*
19+
* @var array<string, \Laravel\Socialite\Testing\FakeProvider>
20+
*/
21+
protected $providers = [];
22+
23+
/**
24+
* Create a new Socialite fake instance.
25+
*
26+
* @param \Laravel\Socialite\Contracts\Factory $factory
27+
*/
28+
public function __construct($factory)
29+
{
30+
$this->factory = $factory;
31+
}
32+
33+
/**
34+
* Get an OAuth provider implementation.
35+
*
36+
* @param string $driver
37+
* @return \Laravel\Socialite\Contracts\Provider
38+
*/
39+
public function driver($driver = null)
40+
{
41+
return $this->providers[$driver] ?? $this->factory->driver($driver);
42+
}
43+
44+
/**
45+
* Register a fake user for the given driver.
46+
*
47+
* @param string $driver
48+
* @param \Laravel\Socialite\Contracts\User|\Closure|array|null $user
49+
* @return $this
50+
*/
51+
public function fake($driver, $user = null)
52+
{
53+
$resolver = function () use ($driver) {
54+
return $this->factory->driver($driver);
55+
};
56+
57+
$this->providers[$driver] = new FakeProvider($driver, $resolver, $user);
58+
59+
return $this;
60+
}
61+
}

tests/SocialiteFakeTest.php

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
namespace Laravel\Socialite\Tests;
4+
5+
use Laravel\Socialite\Contracts\Factory;
6+
use Laravel\Socialite\Socialite;
7+
use Laravel\Socialite\SocialiteServiceProvider;
8+
use Laravel\Socialite\Testing\FakeProvider;
9+
use Laravel\Socialite\Testing\SocialiteFake;
10+
use Laravel\Socialite\Two\GoogleProvider;
11+
use Laravel\Socialite\Two\User as OAuth2User;
12+
use Orchestra\Testbench\TestCase;
13+
14+
class SocialiteFakeTest extends TestCase
15+
{
16+
protected function getPackageProviders($app)
17+
{
18+
return [SocialiteServiceProvider::class];
19+
}
20+
21+
protected function tearDown(): void
22+
{
23+
Socialite::clearResolvedInstances();
24+
25+
parent::tearDown();
26+
}
27+
28+
public function test_it_can_fake_a_driver_with_a_user()
29+
{
30+
$user = (new OAuth2User)->map([
31+
'id' => '123',
32+
'name' => 'Test User',
33+
'email' => '[email protected]',
34+
]);
35+
36+
Socialite::fake('github', $user);
37+
38+
$this->assertInstanceOf(SocialiteFake::class, $this->app->make(Factory::class));
39+
$this->assertInstanceOf(FakeProvider::class, Socialite::driver('github'));
40+
41+
$retrievedUser = Socialite::driver('github')->user();
42+
43+
$this->assertSame('123', $retrievedUser->getId());
44+
$this->assertSame('Test User', $retrievedUser->getName());
45+
$this->assertSame('[email protected]', $retrievedUser->getEmail());
46+
}
47+
48+
public function test_it_can_fake_a_driver_with_a_closure()
49+
{
50+
Socialite::fake('github', function () {
51+
return (new OAuth2User)->map([
52+
'id' => '456',
53+
'name' => 'Closure User',
54+
'email' => '[email protected]',
55+
]);
56+
});
57+
58+
$user = Socialite::driver('github')->user();
59+
60+
$this->assertSame('456', $user->getId());
61+
$this->assertSame('Closure User', $user->getName());
62+
}
63+
64+
public function test_it_can_fake_multiple_drivers()
65+
{
66+
Socialite::fake('github', (new OAuth2User)->map(['id' => 'github-123']));
67+
Socialite::fake('google', (new OAuth2User)->map(['id' => 'google-456']));
68+
69+
$this->assertSame('github-123', Socialite::driver('github')->user()->getId());
70+
$this->assertSame('google-456', Socialite::driver('google')->user()->getId());
71+
}
72+
73+
public function test_it_returns_fake_redirect_response()
74+
{
75+
Socialite::fake('github', (new OAuth2User)->map(['id' => '123']));
76+
77+
$response = Socialite::driver('github')->redirect();
78+
79+
$this->assertSame('https://socialite.fake/github/authorize', $response->getTargetUrl());
80+
}
81+
82+
public function test_it_forwards_calls_to_the_real_provider_methods()
83+
{
84+
$this->app['config']->set('services.github', [
85+
'client_id' => 'test-client-id',
86+
'client_secret' => 'test-client-secret',
87+
'redirect' => 'http://localhost/callback',
88+
]);
89+
90+
Socialite::fake('github', (new OAuth2User)->map(['id' => '123']));
91+
92+
$provider = Socialite::driver('github');
93+
94+
// Verify that methods are forwarded to the real provider
95+
$provider->stateless();
96+
$provider->scopes(['user', 'repo']);
97+
$provider->setScopes(['user:email']);
98+
$provider->redirectUrl('http://example.com/callback');
99+
$provider->with(['custom' => 'param']);
100+
$provider->enablePKCE();
101+
102+
// Verify that the fake user is returned despite calling other methods
103+
$user = $provider->user();
104+
105+
$this->assertSame('123', $user->getId());
106+
}
107+
108+
public function test_it_returns_real_driver_when_not_faked()
109+
{
110+
$this->app['config']->set('services.github', [
111+
'client_id' => 'test-client-id',
112+
'client_secret' => 'test-client-secret',
113+
'redirect' => 'http://localhost/callback',
114+
]);
115+
116+
$this->app['config']->set('services.google', [
117+
'client_id' => 'test-client-id',
118+
'client_secret' => 'test-client-secret',
119+
'redirect' => 'http://localhost/callback',
120+
]);
121+
122+
// Fake only github
123+
Socialite::fake('github', (new OAuth2User)->map(['id' => '123']));
124+
125+
// Github should return the fake provider
126+
$this->assertInstanceOf(FakeProvider::class, Socialite::driver('github'));
127+
128+
// Google should return the real provider since it wasn't faked
129+
$this->assertInstanceOf(GoogleProvider::class, Socialite::driver('google'));
130+
}
131+
}

0 commit comments

Comments
 (0)