1

我想在 phpunit 中对控制器进行功能测试时禁用数据库写入。

在 Symfony2 的一个项目中,我使用 phpunit 测试对保存到数据库的 API 端点的 POST 调用。FOSUser这有效,我现在在激活+时遇到回归问题HWIOAuth

为了避免 phpunit 将虚拟数据写入数据库,我在这里使用了这个技巧:Testing Controllers in Symfony2 with Doctrine to mock the entity manager,然后将模拟后的服务注入到测试系统的容器中。

$entityManagerMock->expects($this->once())->method('flush');这样,当您运行测试时,数据库端点会被模拟,并且由于您可以在该问题和下面的代码中看到,它只测试了一个 flush() 被调用。

这曾经奏效。

这个测试给出了绿色条,测试了 POST 调用,没有数据保存到数据库中。

public function testPostCreatesIssue()
{
    $client = static::createClient();

    $entityManagerMock = $this->getMockBuilder( 'Doctrine\ORM\EntityManager' )
        ->setMethods( array( 'persist', 'flush' ) )
        ->disableOriginalConstructor()
        ->getMock();

    $entityManagerMock->expects( $this->once() )
        ->method( 'flush' );

    $client->getContainer()->set( 'doctrine.orm.default_entity_manager', $entityManagerMock );

    $postData = array
    (
        'title' => 'Issues controller test, post creates issue',
        'body' => 'The test tests that a post call' . PHP_EOL . 'creates a new issue' . PHP_EOL . 'in the database.' . PHP_EOL . 'UTF8: áéíóú àèìòù ñç',
        'pageUrl' => 'file://Xmontero/AntiqueCrayon/MainBundle/Tests/Controller/IssuesControllerTest.php'
    );
    $crawler = $client->request( 'POST', '/issues/', $postData );
    $response = $client->getResponse();

    $this->assertEquals( Response::HTTP_CREATED, $response->getStatusCode() );
    $this->assertEquals( 'application/json', $response->headers->get( 'content-type' ) );
}

那时,我只有默认的捆绑包+我自己的捆绑包AppKernel.php

问题

当我在AppKernel.php

new FOS\UserBundle\FOSUserBundle(),
new HWI\Bundle\OAuthBundle\HWIOAuthBundle(),

配置后,在我的浏览器中手动测试一切正常,包括执行上述 POST 测试的 ajax 调用。该控制器与 FOSUser 和 HWIOAuth 无关,因为该控制器在我在内核中激活这些捆绑包之前工作。

但是,尽管手动测试确实有效,但自动化测试开始失败,而Call to a member function getRepository() on a non-object不是在我的代码行中,而是在 FOS 和 Doctrine 的内部 - 这是完整的输出:

xavi@bromo:/files/custom_www/antique-crayon/preproduction$ phpunit -c app --filter Post src/Xmontero/AntiqueCrayon/MainBundle/Tests/Controller/IssuesControllerTest.php
PHPUnit 3.6.10 by Sebastian Bergmann.

Configuration read from /files/custom_www/antique-crayon/preproduction/app/phpunit.xml.dist

PHP Fatal error:  Call to a member function getRepository() on a non-object in /files/custom_www/antique-crayon/preproduction/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php on line 759
PHP Stack trace:
PHP   1. {main}() /usr/bin/phpunit:0
PHP   2. PHPUnit_TextUI_Command::main() /usr/bin/phpunit:46
PHP   3. PHPUnit_TextUI_Command->run() /usr/share/php/PHPUnit/TextUI/Command.php:130
PHP   4. PHPUnit_TextUI_TestRunner->doRun() /usr/share/php/PHPUnit/TextUI/Command.php:192
PHP   5. PHPUnit_Framework_TestSuite->run() /usr/share/php/PHPUnit/TextUI/TestRunner.php:325
PHP   6. PHPUnit_Framework_TestSuite->runTest() /usr/share/php/PHPUnit/Framework/TestSuite.php:745
PHP   7. PHPUnit_Framework_TestCase->run() /usr/share/php/PHPUnit/Framework/TestSuite.php:772
PHP   8. PHPUnit_Framework_TestResult->run() /usr/share/php/PHPUnit/Framework/TestCase.php:751
PHP   9. PHPUnit_Framework_TestCase->runBare() /usr/share/php/PHPUnit/Framework/TestResult.php:649
PHP  10. PHPUnit_Framework_TestCase->runTest() /usr/share/php/PHPUnit/Framework/TestCase.php:804
PHP  11. ReflectionMethod->invokeArgs() /usr/share/php/PHPUnit/Framework/TestCase.php:942
PHP  12. Xmontero\AntiqueCrayon\MainBundle\Tests\Controller\IssuesControllerTest->testPostCreatesIssue() /files/custom_www/antique-crayon/preproduction/src/Xmontero/AntiqueCrayon/MainBundle/Tests/Controller/IssuesControllerTest.php:0
PHP  13. Symfony\Component\BrowserKit\Client->request() /files/custom_www/antique-crayon/preproduction/src/Xmontero/AntiqueCrayon/MainBundle/Tests/Controller/IssuesControllerTest.php:41
PHP  14. Symfony\Bundle\FrameworkBundle\Client->doRequest() /files/custom_www/antique-crayon/preproduction/vendor/symfony/symfony/src/Symfony/Component/BrowserKit/Client.php:327
PHP  15. Symfony\Component\HttpKernel\Client->doRequest() /files/custom_www/antique-crayon/preproduction/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Client.php:111
PHP  16. Symfony\Component\HttpKernel\Kernel->handle() /files/custom_www/antique-crayon/preproduction/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Client.php:81
PHP  17. Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel->handle() /files/custom_www/antique-crayon/preproduction/app/bootstrap.php.cache:2330
PHP  18. Symfony\Component\HttpKernel\HttpKernel->handle() /files/custom_www/antique-crayon/preproduction/app/bootstrap.php.cache:3080
PHP  19. Symfony\Component\HttpKernel\HttpKernel->handleRaw() /files/custom_www/antique-crayon/preproduction/app/bootstrap.php.cache:2931
PHP  20. Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher->dispatch() /files/custom_www/antique-crayon/preproduction/app/bootstrap.php.cache:2958
PHP  21. Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher->preProcess() /files/custom_www/antique-crayon/preproduction/vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php:107
PHP  22. Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher->getListeners() /files/custom_www/antique-crayon/preproduction/vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php:215
PHP  23. Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher->lazyLoad() /files/custom_www/antique-crayon/preproduction/vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php:128
PHP  24. Symfony\Component\DependencyInjection\Container->get() /files/custom_www/antique-crayon/preproduction/vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php:188
PHP  25. appTestDebugProjectContainer->getProfilerListenerService() /files/custom_www/antique-crayon/preproduction/app/bootstrap.php.cache:2056
PHP  26. Symfony\Component\DependencyInjection\Container->get() /files/custom_www/antique-crayon/preproduction/app/cache/test/appTestDebugProjectContainer.php:2251
PHP  27. appTestDebugProjectContainer->getProfilerService() /files/custom_www/antique-crayon/preproduction/app/bootstrap.php.cache:2056
PHP  28. Symfony\Component\DependencyInjection\Container->get() /files/custom_www/antique-crayon/preproduction/app/cache/test/appTestDebugProjectContainer.php:2234
PHP  29. appTestDebugProjectContainer->getSecurity_ContextService() /files/custom_www/antique-crayon/preproduction/app/bootstrap.php.cache:2056
PHP  30. Symfony\Component\DependencyInjection\Container->get() /files/custom_www/antique-crayon/preproduction/app/cache/test/appTestDebugProjectContainer.php:2374
PHP  31. appTestDebugProjectContainer->getSecurity_Authentication_ManagerService() /files/custom_www/antique-crayon/preproduction/app/bootstrap.php.cache:2056
PHP  32. Symfony\Component\DependencyInjection\Container->get() /files/custom_www/antique-crayon/preproduction/app/cache/test/appTestDebugProjectContainer.php:3941
PHP  33. appTestDebugProjectContainer->getFosUser_UserProvider_UsernameEmailService() /files/custom_www/antique-crayon/preproduction/app/bootstrap.php.cache:2056
PHP  34. Symfony\Component\DependencyInjection\Container->get() /files/custom_www/antique-crayon/preproduction/app/cache/test/appTestDebugProjectContainer.php:3885
PHP  35. appTestDebugProjectContainer->getFosUser_UserManagerService() /files/custom_www/antique-crayon/preproduction/app/bootstrap.php.cache:2056
PHP  36. FOS\UserBundle\Doctrine\UserManager->__construct() /files/custom_www/antique-crayon/preproduction/app/cache/test/appTestDebugProjectContainer.php:1657
PHP  37. Doctrine\ORM\EntityManager->getRepository() /files/custom_www/antique-crayon/preproduction/vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Doctrine/UserManager.php:40
xavi@bromo:/files/custom_www/antique-crayon/preproduction$

缩小问题范围

问题是doctrine.orm.default_entity_manager我在测试阶段为其注入模拟的服务,这就是浏览器中的手动测试没有失败的原因:没有模拟,那里。

如果我编辑我的测试不使用任何模拟,只需写入真实的数据库,它就会起作用:

->set()要在没有模拟的情况下进行测试,我只需在容器中注释掉。并且为了避免Method was expected to be called 1 times, actually called 0 times.我还注释掉->expects()

    //$entityManagerMock->expects( $this->once() )
    //  ->method( 'flush' );
    //
    //$client->getContainer()->set( 'doctrine.orm.default_entity_manager', $entityManagerMock );

然后测试绿条:

xavi@bromo:/files/custom_www/antique-crayon/preproduction$ phpunit -c app --filter Post src/Xmontero/AntiqueCrayon/MainBundle/Tests/Controller/IssuesControllerTest.php
PHPUnit 3.6.10 by Sebastian Bergmann.

Configuration read from /files/custom_www/antique-crayon/preproduction/app/phpunit.xml.dist

.

Time: 0 seconds, Memory: 32.50Mb

OK (1 test, 2 assertions)
xavi@bromo:/files/custom_www/antique-crayon/preproduction$

结论

  • doctrine.orm.default_entity_manager预激活 FOSUser 和 HWIOAuth 中注入模拟,工作正常
  • 当在技巧中激活这些捆绑包时AppKernel不起作用。
  • 然后我只能测试允许将真实数据写入数据库,这在测试时是不需要的。

问题

当 FOSUserBundle 和 HWIOAuthBundle 在内核中激活时,如何从 phpunit 中的测试调用控制器时禁用对数据库的写入?

谢谢!哈维。

4

1 回答 1

0

我对此并不感到自豪,但我还没有用 phpUnit 做这么多的测试来回答你的问题,答案是 100% 的安全性,但这是我的意见。

我认为解决方案是模拟 entityManager,就像你正在做的那样,但你只模拟了 persist() 和 flush() 方法。

->setMethods( array( 'persist', 'flush' ) )
->disableOriginalConstructor()

如果没有您尝试测试的代码,我无法确定,但似乎在您的控制器的任何部分中,您正在使用 de FOSUserBundle 或 HWIOAuthBundle 并且其中一个或两个至少使用 getRepository()方法,您至少需要模拟可以通过 FOSUserBundle 和 HWIOAuthBundle 使用的所有方法。

如果您没有使用 setMethods() 指示方法,所有这些方法都会被模拟,并且您仍然可以控制方法被调用的次数,如下面的代码示例所示。

$mockObjectManager = $this->getMockBuilder('Doctrine\ORM\EntityManager')
    ->disableOriginalConstructor()
    ->getMock();

$mockObjectManager->expects($this->any())
    ->method('persist');
$mockObjectManager->expects($this->atLeastOnce())
    ->method('flush');

如果 FOSUserBundle 试图获取存储库,看起来,您将不得不模拟该调用的返回,如果您不这样做,它可能会在下一次调用时以错误结束,例如:

$this->em->expects($this->any())
        ->method('getRepository')
        ->with('FOSUserBundle:XXXXX')
        ->will($this->returnValue($this->getMockedClassWithFind('\Doctrine\ORM\EntityRepository')));

我认为最好的解决方案是开始解决第一个错误,您将看到另一个错误,因为 FosUserBundle 期望的对象将为空,并模拟该对象,并且可能期望另一个对象,或者您已经完成。

希望是有帮助的。

于 2014-09-25T14:27:37.183 回答