11

I use Twig_Environment to render html mails to be sent. I have a NotificationService Class which is used by other services to send those mails.

In a normal usage, everything is working, but since updating to 2.8 the unittest fail with: Symfony\Component\DependencyInjection\Exception\RuntimeException: You have requested a synthetic service ("kernel"). The DIC does not know how to construct this service

I debugged the StackTrace and the problem seems to be Twig_Environment (which uses the file_locator which injects kernel)

/**
 * Notification Service Class
 *
 * @DI\Service("app.service.notification")
 */
class NotificationService extends Generic
{
    /**
     * @var \Twig_Environment
     */
    protected $twig;

    /**
     * @var \Swift_Mailer
     */
    protected $swiftMailer;

    /**
     * @var string
     */
    protected $mailTemplate = 'VendorAdminBundle:Email:system2.html.twig';

    /**
     * @param \Swift_Mailer     $swiftMailer
     * @param \Twig_Environment $twig
     *
     * @DI\InjectParams({
     *      "swiftMailer" = @DI\Inject("mailer"),
     *      "twig"   = @DI\Inject("twig")
     * })
     */
    public function __construct(\Swift_Mailer $swiftMailer, \Twig_Environment $twig)
    {
        $this->twig          = $twig;
        $this->swiftMailer   = $swiftMailer;
    }

    /**
     * Send notification mail to Manager
     * @param UserEntity $manager
     * @param array      $contacts
     */
    public function notifyManager(UserEntity $manager, array $contacts)
    {
        $subject = 'Lorem Ipsum';
        $templateFile = "AppBundle:Email:notifyManager.html.twig";
        $templateContent = $this->twig->loadTemplate($templateFile);
        $body = $templateContent->render(array(
            'user'      => $manager,
            'contacts'  => $contacts,
            'subject'   => $subject,
        ));

        $this->sendMail($body, $subject, $manager);
    }
}

Any pointers on how to solve this?

EDIT: (on request)

class NotificationTest extends DoctrineTestCase
{
    /**
     * @var \App\Service\Notification
     */
    protected $service;

    public function setUp()
    {
        $this->markTestSkipped('Problem with twig env');

        $this->loadFixturesFromDirectory(__DIR__ . '/DataFixtures');
        $this->loginUser('admin', $this->getUser(1));
        $this->service = $this->container->get('neos.service.notification'); // <-- exception thrown here
    }

    [test methods]
}   

EDIT2:

/**
 * Class DoctrineTestCase.
 *
 * This is the base class to load doctrine fixtures using the symfony configuration
 */
class DoctrineTestCase extends TestCase
{
    /**
     * @var \Symfony\Component\DependencyInjection\Container
     */
    protected $container;

    /**
     * @var \Doctrine\ORM\EntityManager
     */
    protected $em;

    /**
     * @var string
     */
    protected $environment = 'test';

    /**
     * @var bool
     */
    protected $debug = true;

    /**
     * @var string
     */
    protected $entityManagerServiceId = 'doctrine.orm.entity_manager';

    /**
     * Constructor.
     *
     * @param string|null $name     Test name
     * @param array       $data     Test data
     * @param string      $dataName Data name
     */
    public function __construct($name = null, array $data = array(), $dataName = '')
    {
        parent::__construct($name, $data, $dataName);

        if (!static::$kernel) {
            static::$kernel = self::createKernel(array(
                'environment' => $this->environment,
                'debug' => $this->debug,
            ));
            static::$kernel->boot();
            static::$kernel->getContainer()->set('kernel', static::$kernel); //<--- Added - but doesnt help
        }

        $this->container = static::$kernel->getContainer();
        $this->em = $this->getEntityManager();
    }

    /**
     * Executes fixtures.
     *
     * @param \Doctrine\Common\DataFixtures\Loader $loader
     */
    protected function executeFixtures(Loader $loader)
    {
        $purger = new ORMPurger();
        $executor = new ORMExecutor($this->em, $purger);
        $executor->execute($loader->getFixtures());
    }

    /**
     * Load and execute fixtures from a directory.
     *
     * @param string $directory
     */
    protected function loadFixturesFromDirectory($directory)
    {
        $loader = new ContainerAwareLoader($this->container);
        $loader->loadFromDirectory($directory);
        $this->executeFixtures($loader);
    }

    /**
     * Returns the doctrine orm entity manager.
     *
     * @return object
     */
    protected function getEntityManager()
    {
        return $this->container->get($this->entityManagerServiceId);
    }
}

EDIT3: Geting the Kernel seems to have changed sometimes in the past. see http://symfony.com/doc/master/cookbook/testing/doctrine.html

i changed my constructor from:

public function __construct($name = null, array $data = array(), $dataName = '')
{
    parent::__construct($name, $data, $dataName);

    if (!static::$kernel) {
        static::$kernel = self::createKernel(array(
            'environment' => $this->environment,
            'debug' => $this->debug,
        ));
        static::$kernel->boot();
        static::$kernel->getContainer()->set('kernel', static::$kernel); //<--- Added - but doesnt help
    }

    $this->container = static::$kernel->getContainer();
    $this->em = $this->getEntityManager();
}

to:

public function __construct($name = null, array $data = [], $dataName = '')
{
    parent::__construct($name, $data, $dataName);

    self::bootKernel();

    $this->container = static::$kernel->getContainer();
    $this->em = $this->getEntityManager();
}

but unfortunatly it doesnt fix the RuntimeException when tests use the Twig_Env.

Rufinus
  • 29,200
  • 6
  • 68
  • 84
  • 1
    Maybe [this page](http://symfony.com/doc/2.8/components/dependency_injection/synthetic_services.html) will be helpful. – Radu Murzea Dec 19 '15 at 22:03
  • but shoudnt AppKernel set the kernel instance? the unittests go through the same bootstrap classes. – Rufinus Dec 20 '15 at 08:17
  • Yes, that's right. I don't really know what's going on in your case, I just thought that page might be a good place to start... – Radu Murzea Dec 20 '15 at 09:44
  • Can you show code of one of the failing test classes? – Dan Mironis Dec 22 '15 at 12:35
  • yes, but it will not help, as soon as i request the class from the container the error is thrown. see above. – Rufinus Dec 22 '15 at 13:15
  • 1
    Could you also show us `DoctrineTestCase` class? I have a feeling that something will be wrong with setting up the kernel or getting the container. – Dan Mironis Dec 22 '15 at 18:52
  • hi @Rufinus in order to replicate your scenario, if you comment the line about the fixtureLoad and the user login, the exception went hovenever throw? – Matteo Dec 23 '15 at 07:14
  • @DanMironis DoctrineTestCase added. – Rufinus Dec 23 '15 at 11:18
  • @Matteo tried it, but exception stays the same. – Rufinus Dec 23 '15 at 11:20
  • Did you upgrade your phpunit? Putting phpunit/phpunit in my composer.json and then using vendor/bin/phpunit instead of my system-built-in phpunit helped in my case (I was unable to run Symfony3's test suite after upgrading). – Alain Tiemblo Dec 23 '15 at 18:36
  • @AlainTiemblo i always use a local phpunit (via composer) current version is PHPUnit 5.1.3 – Rufinus Dec 23 '15 at 23:54
  • I don't know if it will be helpful, but I have came across [this page](https://github.com/symfony/symfony/issues/16840), it seems to be very similar problem. Seems like `static::$kernel` may store value inconsistent sometimes, according to the bug reporter. – Dan Mironis Dec 24 '15 at 15:40
  • seems the jms/di-extra-bundle is not yet ready for sf 2.8/3.0 https://github.com/schmittjoh/JMSDiExtraBundle/issues/224 – Matteo Dec 24 '15 at 17:04
  • @Matteo this has nothing todo with DI-Extra. DI is working fine in 2.8 (besides the deprecation warnings) – Rufinus Dec 25 '15 at 12:40
  • 2
    You should not boot the kernel in the test case's constructor. This way it will be shutdown after the first test has been executed (see https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php#L186-L189). You should rather boot the kernel in the test case's `setUp()` method. – xabbuh Dec 25 '15 at 15:20
  • @xabbuh yep this fixes it in 8 out of 10 cases. i guess the other 2 are edge cases and need further debuging. wanna write up an answer so i can award the points? – Rufinus Dec 25 '15 at 23:13
  • @xabbuh it would be good if you could put your comment as an answer. Rufinus I trust you will hold off till he does as it's certainly not right or fair to award points for plagiarism with the obvious attempt by one user already. – Trevor Dec 26 '15 at 05:15
  • @Rufinus Did that. Though I am not sure I properly understood which other two cases you mean. Would you mind creating a fork of the Symfony Standard Edition and make the changes that are needed to reproduce your issue? – xabbuh Dec 26 '15 at 07:59
  • @xabbuh the `NotifcationTest` works, but in another case (deep in a event driven workflow unittest) i have the same problem. but i think this has todo with this event or the service is constructed. i will need to debug this further, thanks again for the pointer.... of course it makes sense and i should have spoted this as soon as i found the cookbook article. ... hours of debuging makes you blind i guess. Thanks again. I awarded you the points as promised. – Rufinus Dec 26 '15 at 08:27

1 Answers1

9

You should not boot the kernel in the test case's constructor. This way it will be shutdown after the first test has been executed (see https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php#L186-L189). You should rather boot the kernel in the test case's setUp() method.

xabbuh
  • 5,801
  • 16
  • 18
  • please, edit your answer according to your comment (you have broken link in your answer) – Sergio Ivanuzzo Dec 26 '15 at 15:31
  • Thank you xabbuh. Exactly our problem. So simple to just not boot the kernel in the `setUpBeforeClass()` but in the `setUp()` Method. – Wulf Aug 08 '17 at 15:56