4
votes

How can I mock a service in a functional test use-case where a "request"(form/submit) is being made. After I make the request all the changes and mocking I made to the container are lost.

I am using Symfony 4 or 5. The code posted here can be also found here: https://github.com/klodoma/symfony-demo

I have the following scenario:

  • SomeActions service is injected into the controller constructor
  • in the functional unit-tests I try to mock the SomeActions functions in order to check that they are executed(it sends an email or something similar)

I mock the service and overwrite it in the unit-tests:

$container->set('App\Model\SomeActions', $someActions);

Now in the tests I do a $client->submit($form); which I know that it terminates the kernel.

My question is: HOW can I inject my mocked $someActions in the container after $client->submit($form);

Below is a sample code I added to the symfony demo app https://github.com/symfony/demo

enter image description here

in services.yaml

App\Model\SomeActions:
    public: true

SomeController.php

<?php

namespace App\Controller;

use App\Model\SomeActions;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

/**
 * Controller used to send some emails
 *
 * @Route("/some")
 */
class SomeController extends AbstractController
{
    private $someActions;

    public function __construct(SomeActions $someActions)
    {
        //just dump the injected class name
        var_dump(get_class($someActions));

        $this->someActions = $someActions;
    }

    /**
     * @Route("/action", methods="GET|POST", name="some_action")
     * @param Request $request
     * @return Response
     */
    public function someAction(Request $request): Response
    {

        $this->someActions->doSomething();

        if ($request->get('send')) {
            $this->someActions->sendEmail();
        }

        return $this->render('default/someAction.html.twig', [
        ]);
    }
}

SomeActions

<?php

namespace App\Model;

use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

class SomeActions
{
    private $mailer;

    public function __construct(MailerInterface $mailer)
    {
        $this->mailer = $mailer;
    }

    public function doSomething()
    {
        echo 'doSomething';
    }

    public function sendEmail()
    {
        echo 'sendEmail';

        $email = (new Email())
            ->from('[email protected]')
            ->to('[email protected]')
            ->subject('Time for Symfony Mailer!')
            ->text('Sending emails is fun again!')
            ->html('<p>See Twig integration for better HTML integration!</p>');
        $this->mailer->send($email);
    }

}

SomeControllerTest.php

<?php

namespace App\Tests\Controller;

use App\Model\SomeActions;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class SomeControllerTest extends WebTestCase
{
    public function testSomeAction()
    {
        $client = static::createClient();

        // gets the special container that allows fetching private services
        $container = self::$container;


        $someActions = $this->getMockBuilder(SomeActions::class)
            ->disableOriginalConstructor()
            ->getMock();

        //expect that sendEmail will be called
        $someActions->expects($this->once())
            ->method('sendEmail');

        //overwrite the default service: class: Mock_SomeActions_e68f817a
        $container->set('App\Model\SomeActions', $someActions);


        $crawler = $client->request('GET', '/en/some/action');

        //submit the form
        $form = $crawler->selectButton('submit')->form();

        $client->submit($form);

        //after submit the default class injected in the controller is "App\Model\SomeActions" and not the mocked service
        $response = $client->getResponse();

        $this->assertResponseIsSuccessful($response);

    }
}
1
Does this answer help you? stackoverflow.com/a/19726963/3545751 (Notice last sentence)Beniamin
Not really, what is the solution for it?klodoma
Its just my guessing - but you probably need to inject mocked service twice (before getting the page and before submit the form).Beniamin
$client->disableReboot();$client->submit($form); If I do this, then I kind of achieve what I wanted. Not sure if it's the best way to do it.klodoma

1 Answers

6
votes

The solution is to disable the kernel reboot:

$client->disableReboot();

It makes sense if ones digs deep enough to understand what's going on under the hood; I am still not sure if there isn't a more straight forward answer.

public function testSomeAction()
{
    $client = static::createClient();
    $client->disableReboot();
...