如何测试 Laravel Socialite
How to Test Laravel Socialite
我有一个使用 socialite 的应用程序,我想为 Github 身份验证创建测试,所以我使用 Socialite Facade 来模拟调用 Socialite driver
方法,但是当我 运行 我的测试时,它告诉我我正在尝试获取 null 类型的值。
下面是我写的测试
public function testGithubLogin()
{
Socialite::shouldReceive('driver')
->with('github')
->once();
$this->call('GET', '/github/authorize')->isRedirection();
}
下面是测试的实现
public function authorizeProvider($provider)
{
return Socialite::driver($provider)->redirect();
}
我明白为什么它会 return 这样的结果,因为 Sociallite::driver($provider)
return 是 Laravel\Socialite\Two\GithubProvider
的一个实例,考虑到我无法实例化这个值,它将不可能指定 return 类型。我需要帮助才能成功测试控制器。谢谢
$provider = Mockery::mock('Laravel\Socialite\Contracts\Provider');
$provider->shouldReceive('redirect')->andReturn('Redirected');
$providerName = class_basename($provider);
//Call your model factory here
$socialAccount = factory('LearnCast\User')->create(['provider' => $providerName]);
$abstractUser = Mockery::mock('Laravel\Socialite\Two\User');
// Get the api user object here
$abstractUser->shouldReceive('getId')
->andReturn($socialAccount->provider_user_id)
->shouldReceive('getEmail')
->andReturn(str_random(10).'@noemail.app')
->shouldReceive('getNickname')
->andReturn('Laztopaz')
->shouldReceive('getAvatar')
->andReturn('https://en.gravatar.com/userimage');
$provider = Mockery::mock('Laravel\Socialite\Contracts\Provider');
$provider->shouldReceive('user')->andReturn($abstractUser);
Socialite::shouldReceive('driver')->with('facebook')->andReturn($provider);
// After Oauth redirect back to the route
$this->visit('/auth/facebook/callback')
// See the page that the user login into
->seePageIs('/');
注意:use
socialite 包位于 class
的顶部
use Laravel\Socialite\Facades\Socialite;
我遇到了同样的问题,但是我能够使用上面的技术解决它; @ceejayoz。希望对您有所帮助。
这可能更难做到,但我相信它可以使测试更具可读性。希望您能帮助我简化我将要描述的内容。
我的想法是存根 http 请求。考虑到 facebook,其中有两个:1)/oauth/access_token
(获取访问令牌),2)/me
(获取有关用户的数据)。
为此,我暂时将 php
附加到 mitmproxy
以创建 vcr
fixture:
告诉 php
使用 http 代理(将以下行添加到 .env
文件):
HTTP_PROXY=http://localhost:8080
HTTPS_PROXY=http://localhost:8080
告诉php
代理的证书在哪里:将openssl.cafile = /etc/php/mitmproxy-ca-cert.pem
添加到php.ini
。或者 curl.cainfo
,就此而言。
- 重启
php-fpm
.
- 开始
mitmproxy
.
让您的浏览器也通过 mitmproxy
连接。
使用 facebook 登录您正在开发的站点(此处没有 TDD)。
在 mitmproxy
中按 z
(C
表示 mitmproxy
< 0.18)以在需要时在重定向到 facebook 之前清除请求(流)列表。或者,使用 f
命令(l
for mitmproxy
< 0.18)和 graph.facebook.com
来过滤掉额外的请求。
请注意,对于 Twitter,您需要 league/oauth1-client
1.7 或更新版本。一个从 guzzle/guzzle
切换到 guzzlehttp/guzzle
。否则您将无法登录。
将数据从 mimtproxy
复制到 tests/fixtures/facebook
。我使用了 yaml
格式,下面是它的样子:
-
request:
method: GET
url: https://graph.facebook.com/oauth/access_token?client_id=...&client_secret=...&code=...&redirect_uri=...
response:
status:
http_version: '1.1'
code: 200
message: OK
body: access_token=...&expires=...
-
request:
method: GET
url: https://graph.facebook.com/v2.5/me?access_token=...&appsecret_proof=...&fields=first_name,last_name,email,gender,verified
response:
status:
http_version: '1.1'
code: 200
message: OK
body: '{"first_name":"...","last_name":"...","email":"...","gender":"...","verified":true,"id":"..."}'
为此你可以使用命令 E
如果你有 mitmproxy
>= 0.18。或者,使用命令 P
。它将 request/response 复制到剪贴板。如果您希望 mitmproxy
将它们直接保存到文件中,您可以 运行 使用 DISPLAY= mitmproxy
。
我没有办法使用 php-vcr
的录音工具,因为我没有测试整个工作流程。
这样我就可以编写以下测试(是的,所有这些值都被点替换就没问题了,请按原样随意复制)。
请注意 不过,固定装置取决于 laravel/socialite
的版本。我在使用 Facebook 时遇到了问题。在版本 2.0.16
laravel/socialite
中开始在 facebook urls.
中做 post requests to get access token. Also there's api version
这些灯具用于 2.0.14
。处理它的一种方法是在 composer.json
文件的 require-dev
部分中也有 laravel/socialite
依赖项(具有严格的版本规范)以确保 socialite
是正确的版本开发环境(希望,composer
将忽略生产环境中 require-dev
部分中的那个。)考虑到你在生产环境中做 composer install --no-dev
。
AuthController_HandleFacebookCallbackTest.php
:
<?php
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Auth;
use VCR\VCR;
use App\User;
class AuthController_HandleFacebookCallbackTest extends TestCase
{
use DatabaseTransactions;
static function setUpBeforeClass()
{
VCR::configure()->enableLibraryHooks(['stream_wrapper', 'curl'])
->enableRequestMatchers([
'method',
'url',
]);
}
/**
* @vcr facebook
*/
function testCreatesUserWithCorrespondingName()
{
$this->doCallbackRequest();
$this->assertEquals('John Doe', User::first()->name);
}
/**
* @vcr facebook
*/
function testCreatesUserWithCorrespondingEmail()
{
$this->doCallbackRequest();
$this->assertEquals('john.doe@gmail.com', User::first()->email);
}
/**
* @vcr facebook
*/
function testCreatesUserWithCorrespondingFbId()
{
$this->doCallbackRequest();
$this->assertEquals(123, User::first()->fb_id);
}
/**
* @vcr facebook
*/
function testCreatesUserWithFbData()
{
$this->doCallbackRequest();
$this->assertNotEquals('', User::first()->fb_data);
}
/**
* @vcr facebook
*/
function testRedirectsToHomePage()
{
$this->doCallbackRequest();
$this->assertRedirectedTo('/');
}
/**
* @vcr facebook
*/
function testAuthenticatesUser()
{
$this->doCallbackRequest();
$this->assertEquals(User::first()->id, Auth::user()->id);
}
/**
* @vcr facebook
*/
function testDoesntCreateUserIfAlreadyExists()
{
$user = factory(User::class)->create([
'fb_id' => 123,
]);
$this->doCallbackRequest();
$this->assertEquals(1, User::count());
}
function doCallbackRequest()
{
return $this->withSession([
'state' => '...',
])->get('/auth/facebook/callback?' . http_build_query([
'state' => '...',
]));
}
}
tests/fixtures/facebook
:
-
request:
method: GET
url: https://graph.facebook.com/oauth/access_token
response:
status:
http_version: '1.1'
code: 200
message: OK
body: access_token=...
-
request:
method: GET
url: https://graph.facebook.com/v2.5/me
response:
status:
http_version: '1.1'
code: 200
message: OK
body: '{"first_name":"John","last_name":"Doe","email":"john.doe\u0040gmail.com","id":"123"}'
AuthController_HandleTwitterCallbackTest.php
:
<?php
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Auth;
use VCR\VCR;
use League\OAuth1\Client\Credentials\TemporaryCredentials;
use App\User;
class AuthController_HandleTwitterCallbackTest extends TestCase
{
use DatabaseTransactions;
static function setUpBeforeClass()
{
VCR::configure()->enableLibraryHooks(['stream_wrapper', 'curl'])
->enableRequestMatchers([
'method',
'url',
]);
}
/**
* @vcr twitter
*/
function testCreatesUserWithCorrespondingName()
{
$this->doCallbackRequest();
$this->assertEquals('joe', User::first()->name);
}
/**
* @vcr twitter
*/
function testCreatesUserWithCorrespondingTwId()
{
$this->doCallbackRequest();
$this->assertEquals(123, User::first()->tw_id);
}
/**
* @vcr twitter
*/
function testCreatesUserWithTwData()
{
$this->doCallbackRequest();
$this->assertNotEquals('', User::first()->tw_data);
}
/**
* @vcr twitter
*/
function testRedirectsToHomePage()
{
$this->doCallbackRequest();
$this->assertRedirectedTo('/');
}
/**
* @vcr twitter
*/
function testAuthenticatesUser()
{
$this->doCallbackRequest();
$this->assertEquals(User::first()->id, Auth::user()->id);
}
/**
* @vcr twitter
*/
function testDoesntCreateUserIfAlreadyExists()
{
$user = factory(User::class)->create([
'tw_id' => 123,
]);
$this->doCallbackRequest();
$this->assertEquals(1, User::count());
}
function doCallbackRequest()
{
$temporaryCredentials = new TemporaryCredentials();
$temporaryCredentials->setIdentifier('...');
$temporaryCredentials->setSecret('...');
return $this->withSession([
'oauth.temp' => $temporaryCredentials,
])->get('/auth/twitter/callback?' . http_build_query([
'oauth_token' => '...',
'oauth_verifier' => '...',
]));
}
}
tests/fixtures/twitter
:
-
request:
method: POST
url: https://api.twitter.com/oauth/access_token
response:
status:
http_version: '1.1'
code: 200
message: OK
body: oauth_token=...&oauth_token_secret=...
-
request:
method: GET
url: https://api.twitter.com/1.1/account/verify_credentials.json
response:
status:
http_version: '1.1'
code: 200
message: OK
body: '{"id_str":"123","name":"joe","screen_name":"joe","location":"","description":"","profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/456\/userpic.png"}'
AuthController_HandleGoogleCallbackTest.php
:
<?php
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Auth;
use VCR\VCR;
use App\User;
class AuthController_HandleGoogleCallbackTest extends TestCase
{
use DatabaseTransactions;
static function setUpBeforeClass()
{
VCR::configure()->enableLibraryHooks(['stream_wrapper', 'curl'])
->enableRequestMatchers([
'method',
'url',
]);
}
/**
* @vcr google
*/
function testCreatesUserWithCorrespondingName()
{
$this->doCallbackRequest();
$this->assertEquals('John Doe', User::first()->name);
}
/**
* @vcr google
*/
function testCreatesUserWithCorrespondingEmail()
{
$this->doCallbackRequest();
$this->assertEquals('john.doe@gmail.com', User::first()->email);
}
/**
* @vcr google
*/
function testCreatesUserWithCorrespondingGpId()
{
$this->doCallbackRequest();
$this->assertEquals(123, User::first()->gp_id);
}
/**
* @vcr google
*/
function testCreatesUserWithGpData()
{
$this->doCallbackRequest();
$this->assertNotEquals('', User::first()->gp_data);
}
/**
* @vcr google
*/
function testRedirectsToHomePage()
{
$this->doCallbackRequest();
$this->assertRedirectedTo('/');
}
/**
* @vcr google
*/
function testAuthenticatesUser()
{
$this->doCallbackRequest();
$this->assertEquals(User::first()->id, Auth::user()->id);
}
/**
* @vcr google
*/
function testDoesntCreateUserIfAlreadyExists()
{
$user = factory(User::class)->create([
'gp_id' => 123,
]);
$this->doCallbackRequest();
$this->assertEquals(1, User::count());
}
function doCallbackRequest()
{
return $this->withSession([
'state' => '...',
])->get('/auth/google/callback?' . http_build_query([
'state' => '...',
]));
}
}
tests/fixtures/google
:
-
request:
method: POST
url: https://accounts.google.com/o/oauth2/token
response:
status:
http_version: '1.1'
code: 200
message: OK
body: access_token=...
-
request:
method: GET
url: https://www.googleapis.com/plus/v1/people/me
response:
status:
http_version: '1.1'
code: 200
message: OK
body: '{"emails":[{"value":"john.doe@gmail.com"}],"id":"123","displayName":"John Doe","image":{"url":"https://googleusercontent.com/photo.jpg"}}'
注意。确保您有 php-vcr/phpunit-testlistener-vcr
要求,并且您的 phpunit.xml
中有以下行:
<listeners>
<listener class="PHPUnit_Util_Log_VCR" file="vendor/php-vcr/phpunit-testlistener-vcr/PHPUnit/Util/Log/VCR.php"/>
</listeners>
运行ning 测试时,$_SERVER['HTTP_HOST']
未设置也存在问题。我在这里谈论 config/services.php
文件,即关于重定向 url。我是这样处理的:
<?php
$app = include dirname(__FILE__) . '/app.php';
return [
...
'facebook' => [
...
'redirect' => (isset($_SERVER['HTTP_HOST']) ? 'http://' . $_SERVER['HTTP_HOST'] : $app['url']) . '/auth/facebook/callback',
],
];
不是特别漂亮,但我没找到更好的方法。我打算在那里使用 config('app.url')
,但它在配置文件中不起作用。
UPD 您可以通过删除此方法、运行ning 测试以及使用 vcr 记录更新灯具的请求部分来摆脱 setUpBeforeClass
部分.实际上,整个事情可能只用 vcr
就可以完成(没有 mitmproxy
)。
好吧,两个答案都很好,但是他们有很多不需要的代码,我可以从他们那里推断出我的答案。
这就是我需要做的。
首先模拟Socialite用户类型
$abstractUser = Mockery::mock('Laravel\Socialite\Two\User')
其次,为其方法调用设置期望值
$abstractUser
->shouldReceive('getId')
->andReturn(rand())
->shouldReceive('getName')
->andReturn(str_random(10))
->shouldReceive('getEmail')
->andReturn(str_random(10) . '@gmail.com')
->shouldReceive('getAvatar')
->andReturn('https://en.gravatar.com/userimage');
第三,你需要模拟provider/user调用
Socialite::shouldReceive('driver->user')->andReturn($abstractUser);
然后最后写下你的断言
$this->visit('/auth/google/callback')
->seePageIs('/')
我实际上已经创建了 class 假 return 一个虚拟用户数据,因为我有兴趣测试我的逻辑,而不是社交名流或供应商是否正常工作。
// This is the fake class that extends the original SocialiteManager
class SocialiteManager extends SocialiteSocialiteManager
{
protected function createFacebookDriver()
{
return $this->buildProvider(
FacebookProvider::class, // This class is a fake that returns dummy user in facebook's format
$this->app->make('config')['services.facebook']
);
}
protected function createGoogleDriver()
{
return $this->buildProvider(
GoogleProvider::class, // This is a fake class that ereturns dummy user in google's format
$this->app->make('config')['services.google']
);
}
}
下面是其中一个虚假提供者的样子:
class FacebookProvider extends SocialiteFacebookProvider
{
protected function getUserByToken($token)
{
return [
'id' => '123123123',
'name' => 'John Doe',
'email' => 'test@test.com',
'avatar' => 'image.jpg',
];
}
}
当然在测试 class 中,我将原来的 SocialiteManager 替换为我的版本:
public function setUp(): void
{
parent::setUp();
$this->app->singleton(Factory::class, function ($app) {
return new SocialiteManager($app);
});
}
这对我来说效果很好。无需模拟任何东西。
我有一个使用 socialite 的应用程序,我想为 Github 身份验证创建测试,所以我使用 Socialite Facade 来模拟调用 Socialite driver
方法,但是当我 运行 我的测试时,它告诉我我正在尝试获取 null 类型的值。
下面是我写的测试
public function testGithubLogin()
{
Socialite::shouldReceive('driver')
->with('github')
->once();
$this->call('GET', '/github/authorize')->isRedirection();
}
下面是测试的实现
public function authorizeProvider($provider)
{
return Socialite::driver($provider)->redirect();
}
我明白为什么它会 return 这样的结果,因为 Sociallite::driver($provider)
return 是 Laravel\Socialite\Two\GithubProvider
的一个实例,考虑到我无法实例化这个值,它将不可能指定 return 类型。我需要帮助才能成功测试控制器。谢谢
$provider = Mockery::mock('Laravel\Socialite\Contracts\Provider');
$provider->shouldReceive('redirect')->andReturn('Redirected');
$providerName = class_basename($provider);
//Call your model factory here
$socialAccount = factory('LearnCast\User')->create(['provider' => $providerName]);
$abstractUser = Mockery::mock('Laravel\Socialite\Two\User');
// Get the api user object here
$abstractUser->shouldReceive('getId')
->andReturn($socialAccount->provider_user_id)
->shouldReceive('getEmail')
->andReturn(str_random(10).'@noemail.app')
->shouldReceive('getNickname')
->andReturn('Laztopaz')
->shouldReceive('getAvatar')
->andReturn('https://en.gravatar.com/userimage');
$provider = Mockery::mock('Laravel\Socialite\Contracts\Provider');
$provider->shouldReceive('user')->andReturn($abstractUser);
Socialite::shouldReceive('driver')->with('facebook')->andReturn($provider);
// After Oauth redirect back to the route
$this->visit('/auth/facebook/callback')
// See the page that the user login into
->seePageIs('/');
注意:use
socialite 包位于 class
use Laravel\Socialite\Facades\Socialite;
我遇到了同样的问题,但是我能够使用上面的技术解决它; @ceejayoz。希望对您有所帮助。
这可能更难做到,但我相信它可以使测试更具可读性。希望您能帮助我简化我将要描述的内容。
我的想法是存根 http 请求。考虑到 facebook,其中有两个:1)/oauth/access_token
(获取访问令牌),2)/me
(获取有关用户的数据)。
为此,我暂时将 php
附加到 mitmproxy
以创建 vcr
fixture:
告诉
php
使用 http 代理(将以下行添加到.env
文件):HTTP_PROXY=http://localhost:8080 HTTPS_PROXY=http://localhost:8080
告诉
php
代理的证书在哪里:将openssl.cafile = /etc/php/mitmproxy-ca-cert.pem
添加到php.ini
。或者curl.cainfo
,就此而言。- 重启
php-fpm
. - 开始
mitmproxy
. 让您的浏览器也通过mitmproxy
连接。使用 facebook 登录您正在开发的站点(此处没有 TDD)。
在
mitmproxy
中按z
(C
表示mitmproxy
< 0.18)以在需要时在重定向到 facebook 之前清除请求(流)列表。或者,使用f
命令(l
formitmproxy
< 0.18)和graph.facebook.com
来过滤掉额外的请求。请注意,对于 Twitter,您需要
league/oauth1-client
1.7 或更新版本。一个从guzzle/guzzle
切换到guzzlehttp/guzzle
。否则您将无法登录。将数据从
mimtproxy
复制到tests/fixtures/facebook
。我使用了yaml
格式,下面是它的样子:- request: method: GET url: https://graph.facebook.com/oauth/access_token?client_id=...&client_secret=...&code=...&redirect_uri=... response: status: http_version: '1.1' code: 200 message: OK body: access_token=...&expires=... - request: method: GET url: https://graph.facebook.com/v2.5/me?access_token=...&appsecret_proof=...&fields=first_name,last_name,email,gender,verified response: status: http_version: '1.1' code: 200 message: OK body: '{"first_name":"...","last_name":"...","email":"...","gender":"...","verified":true,"id":"..."}'
为此你可以使用命令
E
如果你有mitmproxy
>= 0.18。或者,使用命令P
。它将 request/response 复制到剪贴板。如果您希望mitmproxy
将它们直接保存到文件中,您可以 运行 使用DISPLAY= mitmproxy
。我没有办法使用
php-vcr
的录音工具,因为我没有测试整个工作流程。
这样我就可以编写以下测试(是的,所有这些值都被点替换就没问题了,请按原样随意复制)。
请注意 不过,固定装置取决于 laravel/socialite
的版本。我在使用 Facebook 时遇到了问题。在版本 2.0.16
laravel/socialite
中开始在 facebook urls.
这些灯具用于 2.0.14
。处理它的一种方法是在 composer.json
文件的 require-dev
部分中也有 laravel/socialite
依赖项(具有严格的版本规范)以确保 socialite
是正确的版本开发环境(希望,composer
将忽略生产环境中 require-dev
部分中的那个。)考虑到你在生产环境中做 composer install --no-dev
。
AuthController_HandleFacebookCallbackTest.php
:
<?php
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Auth;
use VCR\VCR;
use App\User;
class AuthController_HandleFacebookCallbackTest extends TestCase
{
use DatabaseTransactions;
static function setUpBeforeClass()
{
VCR::configure()->enableLibraryHooks(['stream_wrapper', 'curl'])
->enableRequestMatchers([
'method',
'url',
]);
}
/**
* @vcr facebook
*/
function testCreatesUserWithCorrespondingName()
{
$this->doCallbackRequest();
$this->assertEquals('John Doe', User::first()->name);
}
/**
* @vcr facebook
*/
function testCreatesUserWithCorrespondingEmail()
{
$this->doCallbackRequest();
$this->assertEquals('john.doe@gmail.com', User::first()->email);
}
/**
* @vcr facebook
*/
function testCreatesUserWithCorrespondingFbId()
{
$this->doCallbackRequest();
$this->assertEquals(123, User::first()->fb_id);
}
/**
* @vcr facebook
*/
function testCreatesUserWithFbData()
{
$this->doCallbackRequest();
$this->assertNotEquals('', User::first()->fb_data);
}
/**
* @vcr facebook
*/
function testRedirectsToHomePage()
{
$this->doCallbackRequest();
$this->assertRedirectedTo('/');
}
/**
* @vcr facebook
*/
function testAuthenticatesUser()
{
$this->doCallbackRequest();
$this->assertEquals(User::first()->id, Auth::user()->id);
}
/**
* @vcr facebook
*/
function testDoesntCreateUserIfAlreadyExists()
{
$user = factory(User::class)->create([
'fb_id' => 123,
]);
$this->doCallbackRequest();
$this->assertEquals(1, User::count());
}
function doCallbackRequest()
{
return $this->withSession([
'state' => '...',
])->get('/auth/facebook/callback?' . http_build_query([
'state' => '...',
]));
}
}
tests/fixtures/facebook
:
-
request:
method: GET
url: https://graph.facebook.com/oauth/access_token
response:
status:
http_version: '1.1'
code: 200
message: OK
body: access_token=...
-
request:
method: GET
url: https://graph.facebook.com/v2.5/me
response:
status:
http_version: '1.1'
code: 200
message: OK
body: '{"first_name":"John","last_name":"Doe","email":"john.doe\u0040gmail.com","id":"123"}'
AuthController_HandleTwitterCallbackTest.php
:
<?php
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Auth;
use VCR\VCR;
use League\OAuth1\Client\Credentials\TemporaryCredentials;
use App\User;
class AuthController_HandleTwitterCallbackTest extends TestCase
{
use DatabaseTransactions;
static function setUpBeforeClass()
{
VCR::configure()->enableLibraryHooks(['stream_wrapper', 'curl'])
->enableRequestMatchers([
'method',
'url',
]);
}
/**
* @vcr twitter
*/
function testCreatesUserWithCorrespondingName()
{
$this->doCallbackRequest();
$this->assertEquals('joe', User::first()->name);
}
/**
* @vcr twitter
*/
function testCreatesUserWithCorrespondingTwId()
{
$this->doCallbackRequest();
$this->assertEquals(123, User::first()->tw_id);
}
/**
* @vcr twitter
*/
function testCreatesUserWithTwData()
{
$this->doCallbackRequest();
$this->assertNotEquals('', User::first()->tw_data);
}
/**
* @vcr twitter
*/
function testRedirectsToHomePage()
{
$this->doCallbackRequest();
$this->assertRedirectedTo('/');
}
/**
* @vcr twitter
*/
function testAuthenticatesUser()
{
$this->doCallbackRequest();
$this->assertEquals(User::first()->id, Auth::user()->id);
}
/**
* @vcr twitter
*/
function testDoesntCreateUserIfAlreadyExists()
{
$user = factory(User::class)->create([
'tw_id' => 123,
]);
$this->doCallbackRequest();
$this->assertEquals(1, User::count());
}
function doCallbackRequest()
{
$temporaryCredentials = new TemporaryCredentials();
$temporaryCredentials->setIdentifier('...');
$temporaryCredentials->setSecret('...');
return $this->withSession([
'oauth.temp' => $temporaryCredentials,
])->get('/auth/twitter/callback?' . http_build_query([
'oauth_token' => '...',
'oauth_verifier' => '...',
]));
}
}
tests/fixtures/twitter
:
-
request:
method: POST
url: https://api.twitter.com/oauth/access_token
response:
status:
http_version: '1.1'
code: 200
message: OK
body: oauth_token=...&oauth_token_secret=...
-
request:
method: GET
url: https://api.twitter.com/1.1/account/verify_credentials.json
response:
status:
http_version: '1.1'
code: 200
message: OK
body: '{"id_str":"123","name":"joe","screen_name":"joe","location":"","description":"","profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/456\/userpic.png"}'
AuthController_HandleGoogleCallbackTest.php
:
<?php
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Auth;
use VCR\VCR;
use App\User;
class AuthController_HandleGoogleCallbackTest extends TestCase
{
use DatabaseTransactions;
static function setUpBeforeClass()
{
VCR::configure()->enableLibraryHooks(['stream_wrapper', 'curl'])
->enableRequestMatchers([
'method',
'url',
]);
}
/**
* @vcr google
*/
function testCreatesUserWithCorrespondingName()
{
$this->doCallbackRequest();
$this->assertEquals('John Doe', User::first()->name);
}
/**
* @vcr google
*/
function testCreatesUserWithCorrespondingEmail()
{
$this->doCallbackRequest();
$this->assertEquals('john.doe@gmail.com', User::first()->email);
}
/**
* @vcr google
*/
function testCreatesUserWithCorrespondingGpId()
{
$this->doCallbackRequest();
$this->assertEquals(123, User::first()->gp_id);
}
/**
* @vcr google
*/
function testCreatesUserWithGpData()
{
$this->doCallbackRequest();
$this->assertNotEquals('', User::first()->gp_data);
}
/**
* @vcr google
*/
function testRedirectsToHomePage()
{
$this->doCallbackRequest();
$this->assertRedirectedTo('/');
}
/**
* @vcr google
*/
function testAuthenticatesUser()
{
$this->doCallbackRequest();
$this->assertEquals(User::first()->id, Auth::user()->id);
}
/**
* @vcr google
*/
function testDoesntCreateUserIfAlreadyExists()
{
$user = factory(User::class)->create([
'gp_id' => 123,
]);
$this->doCallbackRequest();
$this->assertEquals(1, User::count());
}
function doCallbackRequest()
{
return $this->withSession([
'state' => '...',
])->get('/auth/google/callback?' . http_build_query([
'state' => '...',
]));
}
}
tests/fixtures/google
:
-
request:
method: POST
url: https://accounts.google.com/o/oauth2/token
response:
status:
http_version: '1.1'
code: 200
message: OK
body: access_token=...
-
request:
method: GET
url: https://www.googleapis.com/plus/v1/people/me
response:
status:
http_version: '1.1'
code: 200
message: OK
body: '{"emails":[{"value":"john.doe@gmail.com"}],"id":"123","displayName":"John Doe","image":{"url":"https://googleusercontent.com/photo.jpg"}}'
注意。确保您有 php-vcr/phpunit-testlistener-vcr
要求,并且您的 phpunit.xml
中有以下行:
<listeners>
<listener class="PHPUnit_Util_Log_VCR" file="vendor/php-vcr/phpunit-testlistener-vcr/PHPUnit/Util/Log/VCR.php"/>
</listeners>
运行ning 测试时,$_SERVER['HTTP_HOST']
未设置也存在问题。我在这里谈论 config/services.php
文件,即关于重定向 url。我是这样处理的:
<?php
$app = include dirname(__FILE__) . '/app.php';
return [
...
'facebook' => [
...
'redirect' => (isset($_SERVER['HTTP_HOST']) ? 'http://' . $_SERVER['HTTP_HOST'] : $app['url']) . '/auth/facebook/callback',
],
];
不是特别漂亮,但我没找到更好的方法。我打算在那里使用 config('app.url')
,但它在配置文件中不起作用。
UPD 您可以通过删除此方法、运行ning 测试以及使用 vcr 记录更新灯具的请求部分来摆脱 setUpBeforeClass
部分.实际上,整个事情可能只用 vcr
就可以完成(没有 mitmproxy
)。
好吧,两个答案都很好,但是他们有很多不需要的代码,我可以从他们那里推断出我的答案。
这就是我需要做的。
首先模拟Socialite用户类型
$abstractUser = Mockery::mock('Laravel\Socialite\Two\User')
其次,为其方法调用设置期望值
$abstractUser
->shouldReceive('getId')
->andReturn(rand())
->shouldReceive('getName')
->andReturn(str_random(10))
->shouldReceive('getEmail')
->andReturn(str_random(10) . '@gmail.com')
->shouldReceive('getAvatar')
->andReturn('https://en.gravatar.com/userimage');
第三,你需要模拟provider/user调用
Socialite::shouldReceive('driver->user')->andReturn($abstractUser);
然后最后写下你的断言
$this->visit('/auth/google/callback')
->seePageIs('/')
我实际上已经创建了 class 假 return 一个虚拟用户数据,因为我有兴趣测试我的逻辑,而不是社交名流或供应商是否正常工作。
// This is the fake class that extends the original SocialiteManager
class SocialiteManager extends SocialiteSocialiteManager
{
protected function createFacebookDriver()
{
return $this->buildProvider(
FacebookProvider::class, // This class is a fake that returns dummy user in facebook's format
$this->app->make('config')['services.facebook']
);
}
protected function createGoogleDriver()
{
return $this->buildProvider(
GoogleProvider::class, // This is a fake class that ereturns dummy user in google's format
$this->app->make('config')['services.google']
);
}
}
下面是其中一个虚假提供者的样子:
class FacebookProvider extends SocialiteFacebookProvider
{
protected function getUserByToken($token)
{
return [
'id' => '123123123',
'name' => 'John Doe',
'email' => 'test@test.com',
'avatar' => 'image.jpg',
];
}
}
当然在测试 class 中,我将原来的 SocialiteManager 替换为我的版本:
public function setUp(): void
{
parent::setUp();
$this->app->singleton(Factory::class, function ($app) {
return new SocialiteManager($app);
});
}
这对我来说效果很好。无需模拟任何东西。