Ошибка «В настоящее время нет доступного сеанса» при тестировании Symfony Live Component

Я столкнулся с проблемой во время теста Symfony LiveComponent с формой.

вот тестовый файл:

// /tests/SomeTest.php
class SomeTest extends WebTestCase
{
    use InteractsWithLiveComponents;

    public function testSomething(): void
    {
        $client = static::createClient();
        $productRepository = static::getContainer()->get(ProductRepository::class);
        $userRepository = static::getContainer()->get(UserRepository::class);

        $product = $productRepository->findOneBy(['name' => 'Chino Beige']);
        $user = $userRepository->findOneBy(['username' => 'admin']);

        $component = $this->createLiveComponent(AddToCart::class, [
            'product' => $product,
        ])->actingAs($user);

        $this->assertInstanceOf(TestLiveComponent::class, $component);
        $this->assertEquals(200, $component->response()->getStatusCode());
    }
}

вот функция, которая создает экземпляр формы в LiveComponent:

// /src/Twig/Components/Form/AddToCart.php
protected function instantiateForm(): FormInterface
    {
        $cartItem = new CartItem();

        return $this->createForm(AddToCartType::class, $cartItem, [
            'product' => $this->product,
            'cart' => $this->cartRepository->findOneBy(['customer' => $this->getUser(), 'catalog' => $this->product->getCatalog()]),
            'view_mode' => $this->product->getCatalog()->getViewMode(),
        ]);
    }

Форма AddToCartType:

<?php

namespace App\Form;

// use [...]

class AddToCartType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('product', EntityType::class, [
                'class' => Product::class,
                'data' => $options['product'],
                'required' => true,
                'constraints' => [
                    new NotBlank(),
                ],
            ])
            ->add('cart', EntityType::class, [
                'class' => Cart::class,
                'data' => $options['cart'],
                'choice_label' => 'id',
                'required' => true,
                'constraints' => [
                    new NotBlank(),
                ],
            ])
        ;

        if ($options['view_mode'] === 'grid') {
            foreach ($options['product']->getMeasures() as $measure) {
                $builder->add('quantity_' . $measure->getId(), IntegerType::class, [
                    'mapped' => false,
                    'label' => $measure->getName(),
                    'data' => 0,
                    'required' => false,
                    'attr' => [
                        'min' => 0,
                    ],
                    'constraints' => [
                        new PositiveOrZero(),
                    ],
                ]);
            }
            $builder->add('total_quantity', HiddenType::class, [
                'mapped' => false,
                'label' => false,
                'required' => true,
                'data' => 0,
                'constraints' => [
                    new TotalQuantity(),
                ],
            ]);
        } else {
            $builder
                ->add('measure', EntityType::class, [
                    'label' => 'app.add_to_cart.measures',
                    'class' => Measure::class,
                    'query_builder' => function (MeasureRepository $measureRepository) use ($options): QueryBuilder {
                        $qb = $measureRepository->createQueryBuilder('m');

                        return $qb->andWhere($qb->expr()->in('m.id', ':ids'))
                            ->setParameter('ids', $options['product']->getMeasures()->map(fn($measure) => $measure->getId()));
                    },
                    'expanded' => true,
                    'required' => true,
                    'placeholder' => false,
                    'constraints' => [
                        new NotBlank(),
                    ],
                ])
                ->add('quantity', IntegerType::class, [
                    'label' => 'app.add_to_cart.quantity',
                    'data' => 0,
                    'attr' => [
                        'min' => 0,
                    ],
                    'required' => true,
                    'constraints' => [
                        new Positive(),
                    ],
                ])
                ->add('receiver', TextType::class, [
                    'label' => 'app.add_to_cart.receiver',
                    'attr' => [
                        'placeholder' => 'app.add_to_cart.receiver',
                    ],
                    'required' => true,
                    'constraints' => [
                        new NotBlank(),
                    ],
                ])
            ;
        }
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => CartItem::class,
            'product' => null,
            'cart' => null,
            'view_mode' => 'classic',
        ]);

        $resolver->setAllowedTypes('product', Product::class);
        $resolver->setAllowedTypes('cart', Cart::class);
        $resolver->setAllowedTypes('view_mode', 'string');
    }
}

трассировка стека:

Symfony\Component\HttpFoundation\Exception\SessionNotFoundException : There is currently no session available.
 .../vendor/symfony/http-foundation/RequestStack.php:105
 ...:vendor/symfony/security-csrf/TokenStorage/SessionTokenStorage.php:104
 .../vendor/symfony/security-csrf/TokenStorage/SessionTokenStorage.php:71
 .../vendor/symfony/security-csrf/CsrfTokenManager.php:69
 .../vendor/symfony/form/Extension/Csrf/Type/FormTypeCsrfExtension.php:78
 .../vendor/symfony/form/ResolvedFormType.php:125
 .../vendor/symfony/form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php:86
 .../vendor/symfony/form/ResolvedFormType.php:119
 .../vendor/symfony/form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php:86
 .../vendor/symfony/form/Form.php:904
 .../vendor/symfony/ux-live-component/src/ComponentWithFormTrait.php:118
 .../vendor/symfony/ux-live-component/src/ComponentWithFormTrait.php:93
 .../vendor/symfony/ux-twig-component/src/ComponentFactory.php:205
 .../vendor/symfony/ux-twig-component/src/ComponentFactory.php:91
 .../vendor/symfony/ux-twig-component/src/ComponentFactory.php:65
 .../vendor/symfony/ux-live-component/src/Test/TestLiveComponent.php:174
 .../vendor/symfony/ux-live-component/src/Test/TestLiveComponent.php:123
 .../vendor/symfony/ux-live-component/src/Test/TestLiveComponent.php:51
 .../tests/SomeTest.php:31

Насколько я мог видеть, ошибка возникает, когда Symfony пытается сгенерировать токен CSRF для формы. в файле /vendor/symfony/security-csrf/CsrfTokenManager.php

public function getToken(string $tokenId): CsrfToken  
{  
    $namespacedId = $this->getNamespace().$tokenId;  
    if ($this->storage->hasToken($namespacedId)) {  
        $value = $this->storage->getToken($namespacedId);  
    } else {  
        $value = $this->generator->generateToken();  
  
        $this->storage->setToken($namespacedId, $value);  
    }  
  
    return new CsrfToken($tokenId, $this->randomize($value));  
}

ему не удается получить токен из $this->storage, потому что namespaceId не существует, но я не знаю, почему!

Я также отмечаю, что компонент очень хорошо работает на веб-странице и что параметры формы в режиме модульного тестирования также хорошо восстанавливаются.

Конверт: Симфония 7.0.6 PHP 8.3 PHP-модуль 9.6.19 Live-компонент Symfony UX 2.17

Вы хоть представляете, от чего это могло произойти?

заранее спасибо

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Symfony Station Communiqué - 17 февраля 2023 г
Symfony Station Communiqué - 17 февраля 2023 г
Это коммюнике первоначально появилось на Symfony Station , вашем источнике передовых новостей Symfony, PHP и кибербезопасности.
Управление ответами api для исключений на Symfony с помощью KernelEvents
Управление ответами api для исключений на Symfony с помощью KernelEvents
Много раз при создании api нам нужно возвращать клиентам разные ответы в зависимости от возникшего исключения.
1
0
122
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

У меня была такая же ошибка, и до сих пор я мог просто применить этот обходной путь. Сделайте это до звонка $this->createLiveComponent()

/* Workaround to have a session from where the form can get the CSRF token */
$session = new Session(new MockFileSessionStorage());
$request = new Request();
$request->setSession($session);
$stack = self::getContainer->get(RequestStack::class);
$stack->push($request);

Другие вопросы по теме