Это может быть сложнее сделать, но я считаю, что это делает тесты более читабельными. Надеюсь, вы поможете мне упростить то, что я собираюсь описать.
Моя идея состоит в том, чтобы заглушить http-запросы. С учетом facebook их два: 1) /oauth/access_token
(для получения токена доступа), 2) /me
(для получения данных о пользователе).
Для этого я временно прикрепил php
к mitmproxy
, чтобы создать фикстуру vcr
:
Скажите 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).
Нажмите z
в mitmproxy
(C
для mitmproxy
‹ 0,18), чтобы очистить список запросов (потоков) перед перенаправлением на facebook, если это необходимо. Либо используйте команду f
(l
для mitmproxy
‹ 0,18) с graph.facebook.com
, чтобы отфильтровать дополнительные запросы.
Обратите внимание, что для твиттера вам понадобится 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
, так как я не тестирую весь рабочий процесс.
С этим я смог написать следующие тесты (и да, они в порядке со всеми этими значениями, замененными точками, не стесняйтесь копировать как есть).
Обратите внимание, однако, фикстуры зависят от версии laravel/socialite
. У меня была проблема с фейсбуком. В версии 2.0.16
laravel/socialite
начал делать запросы на публикацию, чтобы получить токен доступа. Также есть версия API в facebook. URL.
Эти приспособления для 2.0.14
. Один из способов справиться с этим - иметь зависимость laravel/socialite
в разделе require-dev
файла composer.json
(со строгой спецификацией версии), чтобы гарантировать, что 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('[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
:
-
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('[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
:
-
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"}}'
Примечание. Убедитесь, что у вас есть 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>
Также была проблема с неустановленным $_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 Вы можете избавиться от части setUpBeforeClass
, удалив этот метод, запустив тесты и обновив часть запроса фикстур с помощью того, что записывает vcr. На самом деле все это можно было бы сделать с помощью одного vcr
(без mitmproxy
).
person
x-yuri
schedule
24.10.2016
Socialite::shouldReceive('driver->redirect')
. - person ceejayoz   schedule 09.02.2016driver->redirect
- person James Okpe George   schedule 09.02.2016