如何测试 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 drivermethod, 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: usethe 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 phpto mitmproxyto create vcrfixture:
对于我暂时附着php于mitmproxy创建vcr夹具:
Tell
phpto use http proxy (add the following lines to the.envfile):HTTP_PROXY=http://localhost:8080 HTTPS_PROXY=http://localhost:8080Tell
phpwhere proxy's certificate is: addopenssl.cafile = /etc/php/mitmproxy-ca-cert.pemtophp.ini. Orcurl.cainfo, for that matter.- Restart
php-fpm. - Start
mitmproxy. Make your browser connect throughmitmproxyas well.Log in to the site you're developing using facebook (no TDD here).
Press
zinmitmproxy(Cformitmproxy< 0.18) to clear request (flow) list before redirecting to facebook if need be. Or alternatively, usefcommand (lformitmproxy< 0.18) withgraph.facebook.comto filter out extra requests.Do note, that for twitter you'll need
league/oauth1-client1.7 or newer. The one switched fromguzzle/guzzletoguzzlehttp/guzzle. Or else you'll be unable to log in.Copy data from
mimtproxytotests/fixtures/facebook. I usedyamlformat 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
Eif you've gotmitmproxy>= 0.18. Alternatively, use commandP. It copies request/response to clipboard. If you wantmitmproxyto 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 之前按
zinmitmproxy(Cformitmproxy< 0.18) 以清除请求(流)列表。或者,使用fcommand (lformitmproxy< 0.18) withgraph.facebook.com过滤掉额外的请求。请注意,对于 Twitter,您需要
league/oauth1-client1.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.16laravel/socialitestarted doing post requeststo get access token. Also there's api versionin facebook urls.
但请注意,装置取决于laravel/socialite的版本。我在 facebook 上遇到了问题。在版本中2.0.16laravel/socialite开始执行发布请求以获取访问令牌。facebook 网址中也有api 版本。
These fixtures are for 2.0.14. One way to deal with it is to have laravel/socialitedependency in require-devsection of composer.jsonfile as well (with strict version specification) to ensure that socialiteis of proper version in development environment (Hopefully, composerwill ignore the one in require-devsection in production environment.) Considering you do composer install --no-devin 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-vcrrequired, 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.phpfile 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 setUpBeforeClasspart by removing this method, running tests, and updating request part of fixtures with what vcr records. Actually, the whole thing might be done with vcralone (no mitmproxy).
UPD您可以setUpBeforeClass通过删除此方法、运行测试以及使用 vcr 记录更新设备的请求部分来摆脱部分。实际上,整个事情可以单独完成vcr(否mitmproxy)。

