Search code examples
phpsymfonytestingdoctrine-ormfunctional-testing

Attach the user in session to the current EntityManager when writing functional tests


I'm writing a functional test for an Action entity having a relationship with the User entity:

<?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;
}

User:

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;
}

And the user is setted in the controller with the following snippet:

<?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);
    }
}

When calling the action with a REST client for example, everything works fine, the relationship between Action and User is persisted correctly.

Now, when testing the action with a functional test, the relationship is not working because of the following error:

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"}).

For my functional test I need to inject a JWT and a session token because my routes are secured by a JWT and I need to have a user in session.

Here is how I inject that:

<?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;
    }
}

Now, the issue is that the injected UsernamePasswordToken contains a User instance which is detached from the current EntityManager, thus resulting in the Doctrine error above.

I could merge the $user object in the postAction method into the EntityManager but I don't want to do that because it means I modify my working code to make a test passes. I've also tried directling merging the $user object in my test into the EntityManager like this:

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

But it's not working either.

So now, I'm stuck, I really don't know what to do except that I need to attach the user in session back to the current EntityManager.


Solution

  • The error message you are getting indicates that the EntityManager contained in the test client's container doesn't know about your User entity. This leads me to believe that the way you are retrieving the User in your createAuthenticatedClient method is using a different EntityManager.

    I suggest you try to use the test kernel's EntityManager to retrieve the User entity instead. You can get it from the test client's container, for example.