title: Mocking a service called by a controller from a WebTestCase
body: <p>I have an API written using Symfony2 that I'm trying to write post hoc  tests for. One of the endpoints uses an email service to send a password reset email to the user. I'd like to mock out this service so that I can check that the right information is sent to the service, and also prevent an email from actually being sent.</p>
  
  <p>Here's the route I'm trying to test:</p>
  
  <pre><code>/**
   * @Route("/me/password/resets")
   * @Method({"POST"})
   */
  public function requestResetAction(Request $request)
  {
      $userRepository = $this-&gt;get('app.repository.user_repository');
      $userPasswordResetRepository = $this-&gt;get('app.repository.user_password_reset_repository');
      $emailService = $this-&gt;get('app.service.email_service');
      $authenticationLimitsService = $this-&gt;get('app.service.authentication_limits_service');
      $now = new \DateTime();
      $requestParams = $this-&gt;getRequestParams($request);
      if (empty($requestParams-&gt;username)) {
          throw new BadRequestHttpException("username parameter is missing");
      }
      $user = $userRepository-&gt;findOneByUsername($requestParams-&gt;username);
      if ($user) {
          if ($authenticationLimitsService-&gt;isUserBanned($user, $now)) {
              throw new BadRequestHttpException("User temporarily banned because of repeated authentication failures");
          }
          $userPasswordResetRepository-&gt;deleteAllForUser($user);
          $reset = $userPasswordResetRepository-&gt;createForUser($user);
          $userPasswordResetRepository-&gt;saveUserPasswordReset($reset);
          $authenticationLimitsService-&gt;logUserAction($user, UserAuthenticationLog::ACTION_PASSWORD_RESET, $now);
          $emailService-&gt;sendPasswordResetEmail($user, $reset);
      }
      // We return 201 Created for every request so that we don't accidently
      // leak the existence of usernames
      return $this-&gt;jsonResponse("Created", $code=201);
  }
  </code></pre>
  
  <p>I then have an <code>ApiTestCase</code> class that extends the Symfony <code>WebTestCase</code> to provide helper methods. This class contains a <code>setup</code> method that tries to mock the email service:</p>
  
  <pre><code>class ApiTestCase extends WebTestCase {
  
      public function setup() {
          $this-&gt;client = static::createClient(array(
              'environment' =&gt; 'test'
          ));
          $mockEmailService = $this-&gt;getMockBuilder(EmailService::class)
              -&gt;disableOriginalConstructor()
              -&gt;getMock();
          $this-&gt;mockEmailService = $mockEmailService;
      }
  </code></pre>
  
  <p>And then in my actual test cases I'm trying to do something like this:</p>
  
  <pre><code>class CreatePasswordResetTest extends ApiTestCase {
  
      public function testSendsEmail() {
          $this-&gt;mockEmailService-&gt;expects($this-&gt;once())
               -&gt;method('sendPasswordResetEmail');
          $this-&gt;post(
              "/me/password/resets",
              array(),
              array("username" =&gt; $this-&gt;user-&gt;getUsername())
          );
      }
  
  }
  </code></pre>
  
  <p>So now the trick is to get the controller to use the mocked version of the email service. I have read about several different ways to achieve this, so far I've not had much luck.</p>
  
  <p><strong>Method 1: Use container->set()</strong></p>
  
  <p>See <a href="https://stackoverflow.com/questions/19726281/how-to-mock-symfony-2-service-in-a-functional-test">How to mock Symfony 2 service in a functional test?</a></p>
  
  <p>In the <code>setup()</code> method tell the container what it should return when it's asked for the email service:</p>
  
  <pre><code>static::$kernel-&gt;getContainer()-&gt;set('app.service.email_service', $this-&gt;mockEmailService);
  # or
  $this-&gt;client-&gt;getContainer()-&gt;set('app.service.email_service', $this-&gt;mockEmailService);
  </code></pre>
  
  <p>This does not effect the controller at all. It still calls the original service. Some write ups I've seen mention that the mocked service is 'reset' after a single call. I'm not even seeing my first call mocked out so I'm not certain this issue is affecting me yet.</p>
  
  <p>Is there another container I should be calling <code>set</code> on?</p>
  
  <p>Or am I mocking out the service too late?</p>
  
  <p><strong>Method 2: AppTestKernel</strong></p>
  
  <p>See: <a href="http://blog.lyrixx.info/2013/04/12/symfony2-how-to-mock-services-during-functional-tests.html" rel="nofollow noreferrer">http://blog.lyrixx.info/2013/04/12/symfony2-how-to-mock-services-during-functional-tests.html</a>
  See: <a href="https://stackoverflow.com/questions/29082802/symfony2-phpunit-functional-test-custom-user-authentication-fails-after-redirect">Symfony2 phpunit functional test custom user authentication fails after redirect (session related)</a></p>
  
  <p>This one pulls me out of my depth when it comes to PHP and Symfony2 stuff (I'm not really a PHP dev).</p>
  
  <p>The goal seems to be to change some kind of foundation class of the website to allow my mock service to be injected very early in the request.</p>
  
  <p>I have a new <code>AppTestKernel</code>:</p>
  
  <pre><code>&lt;?php
  // app/AppTestKernel.php
  require_once __DIR__.'/AppKernel.php';
  class AppTestKernel extends AppKernel
  {
      private $kernelModifier = null;
  
      public function boot()
      {
          parent::boot();
          if ($kernelModifier = $this-&gt;kernelModifier) {
              $kernelModifier($this);
              $this-&gt;kernelModifier = null;
          };
      }
  
      public function setKernelModifier(\Closure $kernelModifier)
      {
          $this-&gt;kernelModifier = $kernelModifier;
  
          // We force the kernel to shutdown to be sure the next request will boot it
          $this-&gt;shutdown();
      }
  }
  </code></pre>
  
  <p>And a new method in my <code>ApiTestCase</code>:</p>
  
  <pre><code>// https://stackoverflow.com/a/19705215
      protected static function getKernelClass(){
          $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : static::getPhpUnitXmlDir();
          $finder = new Finder();
          $finder-&gt;name('*TestKernel.php')-&gt;depth(0)-&gt;in($dir);
          $results = iterator_to_array($finder);
          if (!count($results)) {
              throw new \RuntimeException('Either set KERNEL_DIR in your phpunit.xml according to http://symfony.com/doc/current/book/testing.html#your-first-functional-test or override the WebTestCase::createKernel() method.');
          }
          $file = current($results);
          $class = $file-&gt;getBasename('.php');
          require_once $file;
          return $class;
      }
  </code></pre>
  
  <p>Then I alter my <code>setup()</code> to use the kernel modifier:</p>
  
  <pre><code>public function setup() {
          ...
      $mockEmailService = $this-&gt;getMockBuilder(EmailService::class)
          -&gt;disableOriginalConstructor()
          -&gt;getMock();
      static::$kernel-&gt;setKernelModifier(function($kernel) use ($mockEmailService) {
          $kernel-&gt;getContainer()-&gt;set('app.service.email_service', $mockEmailService);
      });
      $this-&gt;mockEmailService = $mockEmailService;
  }
  </code></pre>
  
  <p>This works! However I now can't access the container in my other tests when I'm trying to do something like this:</p>
  
  <pre><code>$c = $this-&gt;client-&gt;getKernel()-&gt;getContainer();
  $repo = $c-&gt;get('app.repository.user_password_reset_repository');
  $resets = $repo-&gt;findByUser($user);
  </code></pre>
  
  <p>The <code>getContainer()</code> method returns null.</p>
  
  <p>Should I be using the container differently?</p>
  
  <p>Do I need to inject the container into the new kernel? It extends the original kernel so I don't really know why/how it's any different when it comes to the container stuff.</p>
  
  <p><strong>Method 3: Replace the service in config_test.yml</strong></p>
  
  <p>See: <a href="https://stackoverflow.com/questions/15054971/symfony-phpunit-mock-services">Symfony/PHPUnit mock services</a></p>
  
  <p>This method requires that I write a new service class that overrides the email service. Writing a fixed mock class like this seems less useful than a regular dynamic mock. How can I test that certain methods have been called with certain parameters?</p>
  
  <p><strong>Method 4: Setup everything inside the test</strong></p>
  
  <p>Going on @Matteo's suggestion I wrote a test that did this:</p>
  
  <pre><code>public function testSendsEmail() {
      $mockEmailService = $this-&gt;getMockBuilder(EmailService::class)
          -&gt;disableOriginalConstructor()
          -&gt;getMock();
      $mockEmailService-&gt;expects($this-&gt;once())
           -&gt;method('sendPasswordResetEmail');
      static::$kernel-&gt;getContainer()-&gt;set('app.service.email_service', $mockEmailService);
      $this-&gt;client-&gt;getContainer()-&gt;set('app.service.email_service', $mockEmailService);
      $this-&gt;post(
          "/me/password/resets",
          array(),
          array("username" =&gt; $this-&gt;user-&gt;getUsername())
      );
  }
  </code></pre>
  
  <p>This test fails because the expected method <code>sendPasswordResetEmail</code> wasn't called:</p>
  
  <pre><code>There was 1 failure:
  
  1) Tests\Integration\Api\MePassword\CreatePasswordResetTest::testSendsEmail
  Expectation failed for method name is equal to &lt;string:sendPasswordResetEmail&gt; when invoked 1 time(s).
  Method was expected to be called 1 times, actually called 0 times.
  </code></pre>