如何测试 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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 13:12:24  来源:igfitidea点击:

How to Test Laravel Socialite

phplaraveltestingmockery

提问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:

对于我暂时附着phpmitmproxy创建vcr夹具:

  1. Tell phpto use http proxy (add the following lines to the .envfile):

    HTTP_PROXY=http://localhost:8080
    HTTPS_PROXY=http://localhost:8080
    
  2. Tell phpwhere proxy's certificate is: add openssl.cafile = /etc/php/mitmproxy-ca-cert.pemto php.ini. Or curl.cainfo, for that matter.

  3. Restart php-fpm.
  4. Start mitmproxy.
  5. Make your browser connect through mitmproxyas well.
  6. Log in to the site you're developing using facebook (no TDD here).

    Press zin mitmproxy(Cfor mitmproxy< 0.18) to clear request (flow) list before redirecting to facebook if need be. Or alternatively, use fcommand (lfor mitmproxy< 0.18) with graph.facebook.comto filter out extra requests.

    Do note, that for twitter you'll need league/oauth1-client1.7 or newer. The one switched from guzzle/guzzleto guzzlehttp/guzzle. Or else you'll be unable to log in.

  7. Copy data from mimtproxyto tests/fixtures/facebook. I used yamlformat 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 got mitmproxy>= 0.18. Alternatively, use command P. It copies request/response to clipboard. If you want mitmproxyto save them right to file, you can run it with DISPLAY= mitmproxy.

    I see no way to use php-vcr's recording facilities, since I'm not testing the whole workflow.

  1. 告诉php使用 http 代理(在.env文件中添加以下几行):

    HTTP_PROXY=http://localhost:8080
    HTTPS_PROXY=http://localhost:8080
    
  2. 告诉php代理的证书在哪里:添加openssl.cafile = /etc/php/mitmproxy-ca-cert.pemphp.ini. 或者curl.cainfo,就此而言。

  3. 重新启动php-fpm
  4. 开始mitmproxy
  5. 使您的浏览器也通过连接mitmproxy
  6. 使用 facebook 登录到您正在开发的站点(此处没有 TDD)。

    如果需要,在重定向到 facebook 之前按zin mitmproxy( Cfor mitmproxy< 0.18) 以清除请求(流)列表。或者,使用fcommand ( lfor mitmproxy< 0.18) withgraph.facebook.com过滤掉额外的请求。

    请注意,对于 Twitter,您需要league/oauth1-client1.7 或更高版本。一个从 切换guzzle/guzzleguzzlehttp/guzzle。否则您将无法登录。

  7. 将数据从 复制mimtproxytests/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)。