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
相关产品推荐
相关产品推荐

