我正在尝试在我的 PHPUnit WebTestCase 中测试需要会话的方法,但没有成功。
PHP 8.0,Symfony 5.4
这是我的代码:
当用户登录时,我在会话中保存自定义信息:
public function methodCalledAfterLoginSuccess(int $id_internal_network, SessionInterface $session): Response
{
$session->set('current_internal_network',$id_internal_network);
return $this->redirectToRoute("dashboard");
}
在某些控制器中,我得到这样的值:
#[Route('/contract/list', name: 'list_contract')]
public function listContracts(Request $request, SessionInterface $session): Response
{
$currentInternalNetwork = $session->get('current_internal_network');
(...)
一切都很好。然后,我正在设置功能测试:
class SomeController extends WebTestCase
public function setUp(): void
{
$this->client = static::createClient([], ['HTTPS' => true]);
parent::setUp();
}
public function testShowContractSearchForm(): void
{
$session = new Session(new MockFileSessionStorage());
$session->start();
$this->login('admin');
dd($session->get('current_internal_network'));
$this->client->request('GET', '/contract/list');
self::assertResponseIsSuccessful();
}
但是
$session->get('current_internal_network')
是空的
方法
$this->login('admin');
将提交包含正确信息的登录表单,因此我在测试中“登录”,这部分没问题。
我的framework.yaml:
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file
我不需要在测试中专门访问 $session,但方法
listContracts()
需要有一个会话,其中填充了登录部分的正确信息。
我缺少什么?
我遇到了同样的问题,最终实现了我自己的替代方案 KernelBrowser::loginUser()。就我而言,我有一个多租户应用程序,并且我将活动租户的 ID 保留在用户会话中。
仅供参考,这是我的用户案例:
我正在使用一些事件订阅者来验证(1)当前用户有权访问用户会话中的租户,以及(2)转换为 Doctrine 实体的所有请求参数都属于当前活动的租户。
这是我防止人们试图访问他们不应该访问的东西的第一道防线。
在我的功能测试中,我使用 KernelBrowser 来调用类似
/task/1
的 URL。 “任务”属于单个租户,需要经过身份验证的用户才能访问它,因此测试用例必须与经过身份验证的用户以及存储在会话中的租户一起使用。
我不想引入一种仅测试的方式,如何通过其他方式指定租户 ID(例如 /task/1?tenant=1)。我认为这样的事情是一场即将发生的安全灾难。
无论如何,这是在客户端实例中设置正确的会话 cookie 之前创建会话并向其添加一些参数的帮助器方法。
它就像一个魅力,我正在考虑提交一个 PR,其中
KernelBrowser::loginUser()
将接受一组应注入到模拟会话中的属性的键/值对。
<?php
namespace App\Tests;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\TestBrowserToken;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\AbstractBrowser;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\HttpFoundation\Request;
abstract class BaseWebTestCase extends WebTestCase
{
protected function loginUser(KernelBrowser $client, OrganisationUser|User|string $user, string $firewallContext = 'main'): KernelBrowser
{
// If the $user is just a fixture key, we'll try to convert it into an actual entity.
if (is_string($user)) {
$fixture = $this->getFixture($user);
if (!$fixture instanceof User && !$fixture instanceof OrganisationUser) {
throw new Error(sprintf(
'The fixture "%s" must be an instance of User or OrganisationUser, "%s" found.',
$user,
get_debug_type($fixture)
));
}
$user = $fixture;
unset($fixture);
}
$securityUser = $user instanceof User ? $user : $user->getUser();
$token = new TestBrowserToken($securityUser->getRoles(), $securityUser, $firewallContext);
$container = $client->getContainer();
$container->get('security.untracked_token_storage')->setToken($token);
if ($container->has('session.factory')) {
$session = $container->get('session.factory')->createSession();
} elseif ($container->has('session')) {
$session = $container->get('session');
} else {
return $client;
}
$session->set('_security_'.$firewallContext, serialize($token));
// The magic happens here: If the $user is a OrganisationUser, store the Organisation ID in the session that gets picked up by the client later
if ($user instanceof OrganisationUser) {
$session->set(OrganisationService::ACTIVE_ORGANISATION, $user->getOrganisation()->getId());
}
$session->save();
// IMPORTANT: the domain name must be set to localhost, otherwise it does not work
$cookie = new Cookie($session->getName(), $session->getId(), null, null, 'localhost');
// End of magic
$client->getCookieJar()->set($cookie);
return $client;
}
}
关键是设置session ID,同时添加安全相关的东西。
我确信稍后有一种正确的方法可以从代码的任何地方访问客户端会话,但我还没有找到它。我很想知道该怎么做,如果有人知道的话。
希望有帮助。
这应该可以解决问题(仅作为示例,将语言环境保存到会话中):
public function testSomeSessionValue() {
// ...
$client->jsonRequest('GET', '/api/set-locale', ['locale' => 'en_US']);
$sessionLocale = $client->getRequest()->getSession()->get('locale');
$this->assertEquals('en_US', $sessionLocale);
}