如何测试 Laravel Socialite
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/35294257/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to Test Laravel Socialite
提问by James Okpe George
I have an application that makes use of socialite, I want to create test for Github authentication, So I used Socialite Facade to mock call to the Socialite driver
method, but when I run my test it tells me that I am trying to get value on null type.
我有一个使用 socialite 的应用程序,我想为 Github 身份验证创建测试,所以我使用 Socialite Facade 来模拟对 Socialitedriver
方法的调用,但是当我运行我的测试时,它告诉我我正在尝试获取 null 值类型。
Below is the test I have written
下面是我写的测试
public function testGithubLogin()
{
Socialite::shouldReceive('driver')
->with('github')
->once();
$this->call('GET', '/github/authorize')->isRedirection();
}
Below is the implementation of the test
下面是测试的实现
public function authorizeProvider($provider)
{
return Socialite::driver($provider)->redirect();
}
I understand why it might return such result because Sociallite::driver($provider)
returns an instance of Laravel\Socialite\Two\GithubProvider
, and considering that I am unable to instantiate this value it will be impossible to specify a return type. I need help to successfully test the controller. Thanks
我理解为什么它可能会返回这样的结果,因为它Sociallite::driver($provider)
返回 的实例Laravel\Socialite\Two\GithubProvider
,并且考虑到我无法实例化这个值,因此无法指定返回类型。我需要帮助才能成功测试控制器。谢谢
回答by Olotin Temitope
$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('/');
Note: use
the socialite package at the top of your class
注意:use
班级顶部的社交名媛包
use Laravel\Socialite\Facades\Socialite;
使用 Laravel\Socialite\Facades\Socialite;
I had the same problem, but I was able to solve it using the technique above; @ceejayoz. I hope this helps.
我遇到了同样的问题,但我能够使用上述技术解决它;@ceejayoz。我希望这有帮助。
回答by James Okpe George
Well, both answers were great, but they have lots of codes that are not required, and I was able to infer my answer from them.
好吧,这两个答案都很棒,但是它们有很多不需要的代码,我能够从它们中推断出我的答案。
This is all I needed to do.
这就是我需要做的所有事情。
Firstly mock the Socialite User type
首先模拟 Socialite 用户类型
$abstractUser = Mockery::mock('Laravel\Socialite\Two\User')
Second, set the expected values for its method calls
其次,为其方法调用设置期望值
$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');
Thirdly, you need to mock the provider/user call
第三,您需要模拟提供者/用户调用
Socialite::shouldReceive('driver->user')->andReturn($abstractUser);
Then lastly you write your assertions
然后最后你写下你的断言
$this->visit('/auth/google/callback')
->seePageIs('/')
回答by x-yuri
This may be harder to do, but I believe it makes for more readable tests. Hopefully you'll help me simplify what I'm about to describe.
这可能更难做到,但我相信它会使测试更具可读性。希望你能帮助我简化我将要描述的内容。
My idea is to stub http requests. Considering facebook, there are two of them: 1) /oauth/access_token
(to get access token), 2) /me
(to get data about the user).
我的想法是存根 http 请求。考虑到facebook,有两个:1)/oauth/access_token
(获取访问令牌),2)/me
(获取有关用户的数据)。
For that I temporarily attached php
to mitmproxy
to create vcr
fixture:
对于我暂时附着php
于mitmproxy
创建vcr
夹具:
Tell
php
to use http proxy (add the following lines to the.env
file):HTTP_PROXY=http://localhost:8080 HTTPS_PROXY=http://localhost:8080
Tell
php
where proxy's certificate is: addopenssl.cafile = /etc/php/mitmproxy-ca-cert.pem
tophp.ini
. Orcurl.cainfo
, for that matter.- Restart
php-fpm
. - Start
mitmproxy
. Make your browser connect throughmitmproxy
as well.Log in to the site you're developing using facebook (no TDD here).
Press
z
inmitmproxy
(C
formitmproxy
< 0.18) to clear request (flow) list before redirecting to facebook if need be. Or alternatively, usef
command (l
formitmproxy
< 0.18) withgraph.facebook.com
to filter out extra requests.Do note, that for twitter you'll need
league/oauth1-client
1.7 or newer. The one switched fromguzzle/guzzle
toguzzlehttp/guzzle
. Or else you'll be unable to log in.Copy data from
mimtproxy
totests/fixtures/facebook
. I usedyaml
format and here's what it looks like:- 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":"..."}'
For that you can use command
E
if you've gotmitmproxy
>= 0.18. Alternatively, use commandP
. It copies request/response to clipboard. If you wantmitmproxy
to save them right to file, you can run it withDISPLAY= mitmproxy
.I see no way to use
php-vcr
's recording facilities, since I'm not testing the whole workflow.
告诉
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)。
如果需要,在重定向到 facebook 之前按
z
inmitmproxy
(C
formitmproxy
< 0.18) 以清除请求(流)列表。或者,使用f
command (l
formitmproxy
< 0.18) withgraph.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
。它将请求/响应复制到剪贴板。如果要将mitmproxy
它们直接保存到文件中,可以使用DISPLAY= mitmproxy
.我认为没有办法使用
php-vcr
的录音工具,因为我没有测试整个工作流程。
With that I was able to write the following tests (and yes, they are fine with all those values replaced by dots, feel free to copy as is).
有了这个,我就能够编写以下测试(是的,它们可以用点替换所有这些值,可以按原样复制)。
Do notethough, fixtures depend on laravel/socialite
's version. I had an issue with facebook. In version 2.0.16
laravel/socialite
started doing post requeststo get access token. Also there's api versionin facebook urls.
但请注意,装置取决于laravel/socialite
的版本。我在 facebook 上遇到了问题。在版本中2.0.16
laravel/socialite
开始执行发布请求以获取访问令牌。facebook 网址中也有api 版本。
These fixtures are for 2.0.14
. One way to deal with it is to have laravel/socialite
dependency in require-dev
section of composer.json
file as well (with strict version specification) to ensure that socialite
is of proper version in development environment (Hopefully, composer
will ignore the one in require-dev
section in production environment.) Considering you do composer install --no-dev
in production environment.
这些固定装置用于2.0.14
. 处理它的一种方法是laravel/socialite
在文件require-dev
部分也有依赖关系composer.json
(具有严格的版本规范)以确保socialite
开发环境中的版本正确(希望,composer
将忽略require-dev
生产环境中的部分。)考虑到您composer install --no-dev
在生产环境。
AuthController_HandleFacebookCallbackTest.php
:
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('[email protected]', 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
:
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
:
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
:
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
:
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('[email protected]', 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
:
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":"[email protected]"}],"id":"123","displayName":"John Doe","image":{"url":"https://googleusercontent.com/photo.jpg"}}'
Note. Make sure you have php-vcr/phpunit-testlistener-vcr
required, and that you have the following line in your phpunit.xml
:
笔记。确保您有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>
There also was an issue with $_SERVER['HTTP_HOST']
not being set, when running tests. I'm talking about config/services.php
file here, namely about redirect url. I handled it like so:
$_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',
],
];
Not particularly beautiful, but I failed to find a better way. I was going to use config('app.url')
there, but it doesn't work in config files.
不是特别漂亮,但我没能找到更好的方法。我打算在config('app.url')
那里使用,但它在配置文件中不起作用。
UPDYou can get rid of setUpBeforeClass
part by removing this method, running tests, and updating request part of fixtures with what vcr records. Actually, the whole thing might be done with vcr
alone (no mitmproxy
).
UPD您可以setUpBeforeClass
通过删除此方法、运行测试以及使用 vcr 记录更新设备的请求部分来摆脱部分。实际上,整个事情可以单独完成vcr
(否mitmproxy
)。