编写功能测试时,将会话中的用户附加到当前EntityManager



我正在为与User实体有关系的Action实体编写一个功能测试:

<?php
namespace AcmeAppBundleEntity;
/**
 * Class Action
 *
 * @ORMTable()
 * @ORMEntity(repositoryClass="AcmeAppBundleRepositoryActionRepository")
 */
class Action
{
    /**
     * @var int
     *
     * @ORMColumn(type="integer")
     * @ORMId
     * @ORMGeneratedValue(strategy="AUTO")
     */
    private $id;
    /**
     * @var AcmeAppBundleEntityUser
     *
     * @ORMManyToOne(targetEntity="AcmeAppBundleEntityUser", inversedBy="actions")
     * @ORMJoinColumn(name="user_id", referencedColumnName="id")
     */
    private $createdBy;
}

用户:

namespace AcmeAppBundleEntity;
/**
 * @ORMEntity
 * @ORMTable(name="`user`")
 */
class User extends BaseUser
{
    /**
     * @ORMId
     * @ORMColumn(type="integer")
     * @ORMGeneratedValue(strategy="AUTO")
     */
    protected $id;
    /**
     * @var ArrayCollection
     *
     * @ORMOneToMany(targetEntity="Action", mappedBy="createdBy")
     */
    private $actions;
}

用户在控制器中设置如下代码段:

<?php
namespace AcmeApiBundleController;
/**
 *
 * @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客户端调用操作时,一切都很好,ActionUser之间的关系会正确持久化。

现在,当用功能测试测试动作时,由于以下错误,关系不起作用:

通过关系"Acme\AppBundle\entity\Action#createdBy"找到了一个新实体,该实体未配置为级联实体的持久操作:test。要解决此问题:请在此未知实体上显式调用EntityManager#persist(),或者配置cascade在映射中持久化此关联,例如@ManyToOne(..,cascade={"persist"})。

对于我的功能测试,我需要注入JWT和会话令牌,因为我的路由由JWT保护,并且我需要在会话中有一个用户。

以下是我如何注入:

<?php
namespace AcmeApiBundleTests;
class ApiWebTestCase extends WebTestCase
{
    /**
     * @var ReferenceRepository
     */
    protected $fixturesRepo;
    /**
     * @var Client
     */
    protected $authClient;
    /**
     * @var array
     */
    private $fixtures = [];
    protected function setUp()
    {
        $fixtures = array_merge([
            'AcmeAppBundleDataFixturesORMLoadUserData'
        ], $this->fixtures);
        $this->fixturesRepo = $this->loadFixtures($fixtures)->getReferenceRepository();
        $this->authClient = $this->createAuthenticatedClient();
    }
    /**
     * Create a client with a default Authorization header.
     *
     * @return SymfonyBundleFrameworkBundleClient
     */
    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包含一个User实例,该实例与当前的EntityManager分离,从而导致上面的条令错误。

我可以将postAction方法中的$user对象合并到EntityManager中,但我不想这样做,因为这意味着我要修改我的工作代码以使测试通过。我还尝试将测试中的$user对象直接合并到EntityManager中,如下所示:

$em = $client->getContainer()->get('doctrine')->getManager();
$em->merge($user);

但它也不起作用。

所以现在,我被卡住了,我真的不知道该怎么办,除了我需要将会话中的用户连接回当前的EntityManager

您收到的错误消息表明测试客户端容器中包含的EntityManager不知道您的User实体。这让我相信,在createAuthenticatedClient方法中检索User的方式是使用不同的EntityManager。

我建议您尝试使用测试内核的EntityManager来检索User实体。例如,您可以从测试客户端的容器中获取它。

感谢您的推文,我来完成给定的答案,并(尝试)提出一个解决方案,

问题是您的用户不是由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('AcmeAppBundleEntityUser')
        ->findOneBy([], ['id' => DESC]; // Fetch the last created
    // ...        
    return $client;
}

这对你的固定装置来说是一个遗憾(它们更性感),但我看不出有任何方法可以将你的固定设备作为一个真正的入口,因为你无法与测试过的控制器进行更多的交互。

另一种方法是创建一个到登录端点的请求,但这会更难看。

最新更新