如何在PHPUnit WebTestCase中访问Session(Symfony 5)

mo49yndu  于 12个月前  发布在  PHP
关注(0)|答案(2)|浏览(154)

我试图在我的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');将提交一个登录表单与正确的信息,所以我“登录”在我的测试,这部分是好的。
我的框架。yaml:

when@test:
    framework:
        test: true
        session:
            storage_factory_id: session.storage.factory.mock_file

在我的测试中,我不需要特别访问$session,但是方法listContracts()需要有一个会话,其中填充了登录部分的正确信息。
我错过了什么?

py49o6xq

py49o6xq1#

我也遇到了同样的问题,最终实现了我自己的KernelBrowser::loginUser()替代方案。在我的例子中,我有一个多租户应用程序,我在用户会话中保留了活动租户的ID。
只是为了上下文,这里是我的用户案例:
我使用一些事件订阅者来验证(1)当前用户是否可以访问用户会话中的租户,以及(2)所有转换为Doctrine Entities的请求参数都属于当前活动的租户。
这是我的第一道防线,防止人们试图访问他们不应该访问的东西。
在我的功能测试中,我使用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;
    }
}

关键是在向其添加安全相关内容的同时设置会话ID。
我相信有一个正确的方法可以在以后访问客户端会话,从代码的任何地方,但我还没有找到它。
我希望能帮上忙。

bvk5enib

bvk5enib2#

这应该可以做到这一点(作为一个例子,将一个区域设置保存到会话中):

public function testSomeSessionValue() {
     // ...

     $client->jsonRequest('GET', '/api/set-locale', ['locale' => 'en_US']);

     $sessionLocale = $client->getRequest()->getSession()->get('locale');

     $this->assertEquals('en_US', $sessionLocale);
}

相关问题