我想在phpunit中对控制器进行功能测试时禁用数据库写入。
在Symfony2的一个项目中,我使用phpunit测试对保存到数据库的API端点的POST调用。这有效,我现在在激活FOSUser
+ HWIOAuth
时出现回归问题。
为了避免phpunit将伪数据写入数据库,我在这里使用这个技巧:Testing Controllers in Symfony2 with Doctrine来模拟实体管理器,然后将模拟的服务注入到测试系统的容器中。
这样,当您运行测试时,数据库端点被模拟,并且只测试了一个flush(),这要归功于您在该问题和下面的代码中可以看到的$entityManagerMock->expects($this->once())->method('flush');
。 / p>
这曾经有效。
此测试给出了绿色条,测试了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无关,因为该控制器在我激活内核中的这些bundle之前就已经工作了。
但是尽管手动测试确实有效,但自动化测试开始失败,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 );
然后测试greenbars:
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中的测试中调用控制器时禁止写入DataBase?
谢谢! 哈维。
答案 0 :(得分: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期望的对象将为null,并且模拟那个,并且可能期望另一个对象或者你可能已经完成了。 / p>
希望很有帮助。