You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHPUnit测试SessionManager类遇会话问题:如何正确创建会话?

Symfony SessionManager测试问题:会话未找到与启动失败

问题描述

我封装了一个SessionManager类,用于处理对象数组的序列化/反序列化并存储到Session中,但测试时遇到两类错误:

  • 提示无可用会话(SessionNotFoundException);
  • 手动创建会话时,提示会话已启动(RuntimeException)。

测试环境为test,已尝试过相关解决方案但问题仍存在。

相关代码片段

SessionManager类

class SessionManager
{
    public function __construct(private RequestStack $requestStack, private SerializerInterface $serializer)
    {
    }

    public function addAttendee(Attendee $attendee): void
    {
        $session = $this->requestStack->getSession();
        $attendees = $this->getAttendees();
        $session->set('attendees', $this->serializer->serialize([...$attendees, $attendee], 'json'));
    }

    /**
     * @return Attendee[]
     */
    public function getAttendees(): array
    {
        $attendees = $this->requestStack->getSession()->get('attendees');
        return $this->serializer->deserialize($attendees, Attendee::class . '[]', 'json');
    }
}

测试方法及错误

第一种测试方法(无会话错误)

/** @test */
public function it_can_set_an_array_of_attendees_into_the_session()
{
    $sessionManager = static::getContainer()->get(SessionManager::class);
    $attendee = new Attendee('firstname', 'lastname', 'female');
    $sessionManager->addAttendee($attendee);

    $attendees = $sessionManager->getAttendees();
    $this->assertEquals('firstname', $attendees[0]->firstname);
}

错误信息:

1) App\Tests\Controller\SessionManagerTest::it_can_set_an_array_of_attendees_into_the_session
Symfony\Component\HttpFoundation\Exception\SessionNotFoundException: There is currently no session available.

.../vendor/symfony/http-foundation/RequestStack.php:105
.../src/Controller/SessionManager.php:17
.../tests/Controller/SessionManagerTest.php:48
.../vendor/phpunit/phpunit/phpunit:107

第二种测试方法(会话启动失败错误)

/** @test */
public function it_can_set_an_array_of_attendees_into_the_session()
{
    // Create and start session, add to a Request, add Request to RequestStack, set RequestStack into container
    $session = new Session();
    $session->start();
    $request = new Request();
    $request->setSession($session);
    $requestStack = new RequestStack();
    $requestStack->push($request);
    static::getContainer()->set(RequestStack::class, $requestStack);

    $sessionManager = static::getContainer()->get(SessionManager::class);
    $attendee = new Attendee('firstname', 'lastname', 'female');
    $sessionManager->addAttendee($attendee);

    $attendees = $sessionManager->getAttendees();
    $this->assertEquals('firstname', $attendees[0]->firstname);
}

错误信息:

1) App\Tests\Controller\SessionManagerTest::it_can_set_an_array_of_attendees_into_the_session
RuntimeException: Failed to start the session because headers have already been sent by ".../vendor/phpunit/phpunit/src/Util/Printer.php" at line 104.

.../vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php:116
.../vendor/symfony/http-foundation/Session/Session.php:59
.../tests/Controller/SessionManagerTest.php:46
.../vendor/phpunit/phpunit/phpunit:107

解决方案及测试方案

核心问题

你遇到的本质问题是:在PHPUnit测试环境中,原生Session依赖HTTP头,但PHPUnit在输出测试信息时已经发送了内容,导致无法启动原生Session;同时默认测试容器中没有预加载会话到RequestStack中。

方案1:使用Symfony模拟会话存储

Symfony提供了MockArraySessionStorage,这是一个内存中的会话存储,不需要操作HTTP头,完全适配测试场景:

use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;

/** @test */
public function it_can_set_an_array_of_attendees_into_the_session()
{
    // 创建模拟会话存储的Session
    $session = new Session(new MockArraySessionStorage());
    // 不需要调用start(),模拟存储自动可用

    $request = Request::create('/');
    $request->setSession($session);

    $requestStack = new RequestStack();
    $requestStack->push($request);

    // 替换容器中的RequestStack
    static::getContainer()->set(RequestStack::class, $requestStack);

    $sessionManager = static::getContainer()->get(SessionManager::class);
    $attendee = new Attendee('firstname', 'lastname', 'female');
    $sessionManager->addAttendee($attendee);

    $attendees = $sessionManager->getAttendees();
    $this->assertEquals('firstname', $attendees[0]->firstname);
}

方案2:依赖Mock隔离测试

如果只需要验证SessionManager的业务逻辑(序列化/反序列化、会话读写逻辑),可以Mock依赖的RequestStack和SessionInterface,完全脱离真实会话环境:

use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Serializer\SerializerInterface;

/** @test */
public function it_can_set_an_array_of_attendees_into_the_session()
{
    // Mock SessionInterface
    $sessionMock = $this->createMock(SessionInterface::class);
    // Mock RequestStack,使其返回Mock的Session
    $requestStackMock = $this->createMock(RequestStack::class);
    $requestStackMock->method('getSession')->willReturn($sessionMock);

    // 模拟会话初始无attendees数据
    $sessionMock->method('get')->with('attendees')->willReturn(null);

    // 验证set方法是否被正确调用
    $expectedAttendee = new Attendee('firstname', 'lastname', 'female');
    $serializer = static::getContainer()->get(SerializerInterface::class);
    $expectedSerialized = $serializer->serialize([$expectedAttendee], 'json');

    $sessionMock->expects($this->once())
        ->method('set')
        ->with('attendees', $expectedSerialized);

    // 实例化SessionManager并测试
    $sessionManager = new SessionManager($requestStackMock, $serializer);
    $sessionManager->addAttendee($expectedAttendee);
}

修复SessionManager的潜在问题

当前getAttendees方法在会话中无attendees值时,会将null传给deserialize导致报错,需添加空值处理:

/**
 * @return Attendee[]
 */
public function getAttendees(): array
{
    $attendeesData = $this->requestStack->getSession()->get('attendees');
    if (empty($attendeesData)) {
        return [];
    }
    return $this->serializer->deserialize($attendeesData, Attendee::class . '[]', 'json');
}

内容的提问来源于stack exchange,提问作者JasonM

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 00:08:11