在编写功能测试时将会话中的用户附加到当前的 EntityManager
Attach the user in session to the current EntityManager when writing functional tests
我正在为与 User
实体有关系的 Action
实体编写功能测试:
<?php
namespace Acme\AppBundle\Entity;
/**
* Class Action
*
* @ORM\Table()
* @ORM\Entity(repositoryClass="Acme\AppBundle\Repository\ActionRepository")
*/
class Action
{
/**
* @var int
*
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var \Acme\AppBundle\Entity\User
*
* @ORM\ManyToOne(targetEntity="\Acme\AppBundle\Entity\User", inversedBy="actions")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $createdBy;
}
用户:
namespace Acme\AppBundle\Entity;
/**
* @ORM\Entity
* @ORM\Table(name="`user`")
*/
class User extends BaseUser
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="Action", mappedBy="createdBy")
*/
private $actions;
}
并使用以下代码片段在控制器中设置用户:
<?php
namespace Acme\ApiBundle\Controller;
/**
*
* @Route("/actions")
*/
class ActionController extends FOSRestController
{
public function postAction(Request $request)
{
$action = new Action();
$action->setCreatedBy($this->getUser());
return $this->processForm($action, $request->request->all(), Request::METHOD_POST);
}
}
例如,当使用 REST 客户端调用操作时,一切正常,Action
和 User
之间的关系正确保留。
现在,在使用功能测试测试操作时,由于以下错误,关系不起作用:
A new entity was found through the relationship 'Acme\AppBundle\Entity\Action#createdBy' that was not configured to cascade persist operations for entity: test. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"}).
对于我的功能测试,我需要注入一个 JWT 和一个会话令牌,因为我的路由受 JWT 保护并且我需要在会话中有一个用户。
我是这样注入的:
<?php
namespace Acme\ApiBundle\Tests;
class ApiWebTestCase extends WebTestCase
{
/**
* @var ReferenceRepository
*/
protected $fixturesRepo;
/**
* @var Client
*/
protected $authClient;
/**
* @var array
*/
private $fixtures = [];
protected function setUp()
{
$fixtures = array_merge([
'Acme\AppBundle\DataFixtures\ORM\LoadUserData'
], $this->fixtures);
$this->fixturesRepo = $this->loadFixtures($fixtures)->getReferenceRepository();
$this->authClient = $this->createAuthenticatedClient();
}
/**
* Create a client with a default Authorization header.
*
* @return \Symfony\Bundle\FrameworkBundle\Client
*/
protected function createAuthenticatedClient()
{
/** @var User $user */
$user = $this->fixturesRepo->getReference('user-1');
$jwtManager = $this->getContainer()->get('lexik_jwt_authentication.jwt_manager');
$token = $jwtManager->create($user);
$this->loginAs($user, 'api');
$client = static::makeClient([], [
'AUTHENTICATION' => 'Bearer ' . $token,
'CONTENT_TYPE' => 'application/json'
]);
$client->disableReboot();
return $client;
}
}
现在,问题是注入的 UsernamePasswordToken
包含一个与当前 EntityManager
分离的 User
实例,从而导致上述 Doctrine 错误。
我可以将 postAction
方法中的 $user
对象合并到 EntityManager
中,但我不想这样做,因为这意味着我修改了我的工作代码来制作测试通过。
我也试过直接将测试中的 $user
对象合并到 EntityManager
中,如下所示:
$em = $client->getContainer()->get('doctrine')->getManager();
$em->merge($user);
但它也不起作用。
所以现在,我被卡住了,我真的不知道该怎么办,除了我需要将会话中的用户附加回当前 EntityManager
。
您收到的错误消息表明测试客户端容器中包含的 EntityManager 不知道您的用户实体。这让我相信您在 createAuthenticatedClient
方法中检索用户的方式是使用不同的 EntityManager。
我建议您尝试使用测试内核的 EntityManager 来检索用户实体。例如,您可以从测试客户端的容器中获取它。
感谢您的推文,我来完成给定的答案并(尝试)提出解决方案,
问题是您的用户不是由 EntityManager 管理的,更简单的是,因为它不是在数据库中注册的真实现有用户。
要解决这个问题,您需要有一个真实的(受管理的)用户,该原则可以用于您的操作试图创建的关联。
因此,您可以在每次执行功能测试用例时创建此用户(并在完成后将其删除),或者在新环境中首次执行测试用例时仅创建一次。
像这样应该可以解决问题:
/** @var EntityManager */
private $em;
/**
*/
public function setUp()
{
$client = static::createClient();
$this->em = $client->getKernel()
->getContainer()
->get('doctrine');
$this->authClient = $this->createAuthenticatedClient();
}
/**
*/
protected function createAuthenticatedClient()
{
/** @var User $user */
$user = $this->em
->getRepository('Acme\AppBundle\Entity\User')
->findOneBy([], ['id' => DESC]; // Fetch the last created
// ...
return $client;
}
真可惜你的灯具(更性感),但我看不出有什么方法可以将你的灯具附加为真正的条目,因为你不能与测试控制器进行更多交互。
另一种方法是创建一个到您的登录端点的请求,但它会更难看。
我正在为与 User
实体有关系的 Action
实体编写功能测试:
<?php
namespace Acme\AppBundle\Entity;
/**
* Class Action
*
* @ORM\Table()
* @ORM\Entity(repositoryClass="Acme\AppBundle\Repository\ActionRepository")
*/
class Action
{
/**
* @var int
*
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var \Acme\AppBundle\Entity\User
*
* @ORM\ManyToOne(targetEntity="\Acme\AppBundle\Entity\User", inversedBy="actions")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $createdBy;
}
用户:
namespace Acme\AppBundle\Entity;
/**
* @ORM\Entity
* @ORM\Table(name="`user`")
*/
class User extends BaseUser
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="Action", mappedBy="createdBy")
*/
private $actions;
}
并使用以下代码片段在控制器中设置用户:
<?php
namespace Acme\ApiBundle\Controller;
/**
*
* @Route("/actions")
*/
class ActionController extends FOSRestController
{
public function postAction(Request $request)
{
$action = new Action();
$action->setCreatedBy($this->getUser());
return $this->processForm($action, $request->request->all(), Request::METHOD_POST);
}
}
例如,当使用 REST 客户端调用操作时,一切正常,Action
和 User
之间的关系正确保留。
现在,在使用功能测试测试操作时,由于以下错误,关系不起作用:
A new entity was found through the relationship 'Acme\AppBundle\Entity\Action#createdBy' that was not configured to cascade persist operations for entity: test. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"}).
对于我的功能测试,我需要注入一个 JWT 和一个会话令牌,因为我的路由受 JWT 保护并且我需要在会话中有一个用户。
我是这样注入的:
<?php
namespace Acme\ApiBundle\Tests;
class ApiWebTestCase extends WebTestCase
{
/**
* @var ReferenceRepository
*/
protected $fixturesRepo;
/**
* @var Client
*/
protected $authClient;
/**
* @var array
*/
private $fixtures = [];
protected function setUp()
{
$fixtures = array_merge([
'Acme\AppBundle\DataFixtures\ORM\LoadUserData'
], $this->fixtures);
$this->fixturesRepo = $this->loadFixtures($fixtures)->getReferenceRepository();
$this->authClient = $this->createAuthenticatedClient();
}
/**
* Create a client with a default Authorization header.
*
* @return \Symfony\Bundle\FrameworkBundle\Client
*/
protected function createAuthenticatedClient()
{
/** @var User $user */
$user = $this->fixturesRepo->getReference('user-1');
$jwtManager = $this->getContainer()->get('lexik_jwt_authentication.jwt_manager');
$token = $jwtManager->create($user);
$this->loginAs($user, 'api');
$client = static::makeClient([], [
'AUTHENTICATION' => 'Bearer ' . $token,
'CONTENT_TYPE' => 'application/json'
]);
$client->disableReboot();
return $client;
}
}
现在,问题是注入的 UsernamePasswordToken
包含一个与当前 EntityManager
分离的 User
实例,从而导致上述 Doctrine 错误。
我可以将 postAction
方法中的 $user
对象合并到 EntityManager
中,但我不想这样做,因为这意味着我修改了我的工作代码来制作测试通过。
我也试过直接将测试中的 $user
对象合并到 EntityManager
中,如下所示:
$em = $client->getContainer()->get('doctrine')->getManager();
$em->merge($user);
但它也不起作用。
所以现在,我被卡住了,我真的不知道该怎么办,除了我需要将会话中的用户附加回当前 EntityManager
。
您收到的错误消息表明测试客户端容器中包含的 EntityManager 不知道您的用户实体。这让我相信您在 createAuthenticatedClient
方法中检索用户的方式是使用不同的 EntityManager。
我建议您尝试使用测试内核的 EntityManager 来检索用户实体。例如,您可以从测试客户端的容器中获取它。
感谢您的推文,我来完成给定的答案并(尝试)提出解决方案,
问题是您的用户不是由 EntityManager 管理的,更简单的是,因为它不是在数据库中注册的真实现有用户。
要解决这个问题,您需要有一个真实的(受管理的)用户,该原则可以用于您的操作试图创建的关联。
因此,您可以在每次执行功能测试用例时创建此用户(并在完成后将其删除),或者在新环境中首次执行测试用例时仅创建一次。
像这样应该可以解决问题:
/** @var EntityManager */
private $em;
/**
*/
public function setUp()
{
$client = static::createClient();
$this->em = $client->getKernel()
->getContainer()
->get('doctrine');
$this->authClient = $this->createAuthenticatedClient();
}
/**
*/
protected function createAuthenticatedClient()
{
/** @var User $user */
$user = $this->em
->getRepository('Acme\AppBundle\Entity\User')
->findOneBy([], ['id' => DESC]; // Fetch the last created
// ...
return $client;
}
真可惜你的灯具(更性感),但我看不出有什么方法可以将你的灯具附加为真正的条目,因为你不能与测试控制器进行更多交互。
另一种方法是创建一个到您的登录端点的请求,但它会更难看。