After further research, I'm rephrasing my question: How do I mock Doctrine2's entity manager for unit testing, particularly in controllers? Essentially, I need to go through my unit tests and replace anything related to mocking a TableGateway with Doctrine2's entity manager.
(Original Post)
Some time ago I went through the official ZF2 getting started tutorial for version 2.1 and completed it, then went through the additional tutorial concerning unit testing. http://framework.zend.com/manual/2.1/en/user-guide/overview.html http://framework.zend.com/manual/2.1/en/tutorials/unittesting.html
Following the completion of those tutorials, I concluded that Zend_Db was not likely to handle my application needs elegantly and so started looking into integrating and using Doctrine 2 into Zend Framework 2. I struggled to find good examples and then found the following tutorial which was a good start. http://www.jasongrimes.org/2012/01/using-doctrine-2-in-zend-framework-2/
It was here that I got hung up as I wanted to include the Doctrine 2 changes into my unit testing but couldn't figure out how. After many days of fruitless research, I put this project away for sometime and am now coming back to it. I found another decent tutorial about this subject but it concerns ZF1 so the structure is significantly different. http://www.zendcasts.com/unit-testing-doctrine-2-entities/2011/02/
However, with fresh eyes and comparing my sample ZF2 files with the files associated with the ZF1 framework, I see at the very least that I probably need to set up the Doctrine entity manager in the phpunit Bootstrap file and then call it in the Controller tests correctly.
Currently, when I run phpunit, I get the following error:
There was 1 failure:
1) AlbumTest\Controller\AlbumControllerTest::testEditActionRedirectsAfterValidPost
Expectation failed for method name is equal to <string:find> when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
In the relevant part of this test method, I currently have:
//$albumTableMock = $this->getMockBuilder('Album\Models\AlbumTable') // Zend_Db
$albumTableMock = $this->getMockBuilder('Album\Entity\Album')
->disableOriginalConstructor()
->getMock();
(I'm really not sure if 'Album\Entity\Album' is correct here but my understanding is that because of Doctrine 2, I'm no longer using 'Album\Models\Album' or 'Album\Models\AlbumTable' that I created in the initial tutorial.)
In the AlbumController, the relevant portion of code is here:
// Get the Album with the specified id. An exception is thrown
// if it cannot be found, in which case go to the index page.
try {
//$album = $this-> getAlbumTable()->getAlbum($id); // Zend_DB
$album = $this->getEntityManager()->find('Album\Entity\Album', $id); // Doctrine
}
So my main question here would be how would I set up my phpunit Bootstrap file to include Doctrine2?
(Addendum) I'll circle back around to the above scenario now but currently I'm trying to successfully mock the entity manager for my tests and I've commented out the above getMocks in my controller test to get down to basics. I've gotten a basic test set up for Album\Entity\Album so I think my bootstrapping is ok but I'm failing now with tests trying to access the database from the controller when using $this->dispatch()
, which I don't want.
PHPUnit Failure:
1) AlbumTest\Controller\AlbumControllerTest::testIndexActionCanBeAccessed
PDOException: SQLSTATE[HY000] [1045] Access denied for user 'username'@'localhost' (using password: YES)
I don't have this user explicitly defined anywhere so I'm guessing that a config file isn't being found or loaded but I want to mock the database connection anyway.
Here's my test bootstrap:
<?php
namespace AlbumTest;
use Zend\Loader\AutoloaderFactory;
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
//use DoctrineORMModuleTest\Framework\TestCase;
//use Doctrine\Tests\Mocks;
use RuntimeException;
error_reporting(E_ALL | E_STRICT);
chdir(__DIR__);
// Set timezone or PHPUnit reports error
date_default_timezone_set('America/Chicago');
/**
* Test bootstrap, for setting up autoloading
*/
class Bootstrap
{
protected static $serviceManager;
protected static $em; // Doctrine
public static function init()
{
$zf2ModulePaths = array(dirname(dirname(__DIR__)));
if (($path = static::findParentPath('vendor'))) {
$zf2ModulePaths[] = $path;
}
if (($path = static::findParentPath('module')) !== $zf2ModulePaths[0]) {
$zf2ModulePaths[] = $path;
}
static::initAutoloader();
// use ModuleManager to load this module and it's dependencies
$config = array(
'module_listener_options' => array(
'module_paths' => $zf2ModulePaths,
),
'modules' => array(
'DoctrineModule',
'DoctrineORMModule',
'Album'
)
);
$serviceManager = new ServiceManager(new ServiceManagerConfig());
$serviceManager->setService('ApplicationConfig', $config);
$serviceManager->get('ModuleManager')->loadModules();
static::$serviceManager = $serviceManager;
static::$em = self::getEntityManager($serviceManager); // Doctrine
}
public static function getServiceManager()
{
return static::$serviceManager;
}
public static function getEntityManager($serviceManager)
{
return $serviceManager->get('Doctrine\ORM\EntityManager');
}
protected static function initAutoloader()
{
$vendorPath = static::findParentPath('vendor');
$zf2Path = getenv('ZF2_PATH');
if (!$zf2Path) {
if (defined('ZF2_PATH')) {
$zf2Path = ZF2_PATH;
} else {
if (is_dir($vendorPath . '/ZF2/library')) {
$zf2Path = $vendorPath . '/ZF2/library';
}
}
}
if (!$zf2Path) {
throw new RuntimeException('Unable to load ZF2. Run `php composer.phar install` or define a ZF2_PATH environment variable.');
}
include $zf2Path . '/Zend/Loader/AutoloaderFactory.php';
AutoloaderFactory::factory(array(
'Zend\Loader\StandardAutoloader' => array(
'autoregister_zf' => true,
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/' . __NAMESPACE__,
),
),
));
}
protected static function findParentPath($path)
{
// This function traverses up from the working directory
// until it finds the parent of the named path
// (used to find 'vendor' parent in initAutoloader).
$dir = __DIR__;
$previousDir = '.';
while (!is_dir($dir . '/' . $path)) {
$dir = dirname($dir);
if ($previousDir === $dir) return false;
$previousDir = $dir;
}
return $dir . '/' . $path;
}
}
Bootstrap::init();
Here's my Entity test (to hopefully prove Doctrine is bootstrapped correctly):
<?php
namespace AlbumTest\Entity;
use Album\Entity\Album;
use PHPUnit_Framework_TestCase;
class AlbumTest extends PHPUnit_Framework_TestCase
{
public function testAlbumInitialState() {
$album = new Album();
$this->assertNull($album->artist, '"artist" should initially be null');
$this->assertNull($album->id, '"id" should initially be null');
$this->assertNull($album->title, '"title" should initially be null');
}
}
And here's the relevant portions of my Controller Test:
<?php
namespace AlbumTest\Controller;
//use Album\Models\Album;
use Album\Entity\Album;
use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
use Zend\Db\ResultSet\ResultSet;
//use PHPUnit_Framework_TestCase;
class AlbumControllerTest extends AbstractHttpControllerTestCase
{
protected $traceError = true;
public function setUp()
{
// Need to mock entity manager for doctrine use
$this->em = $this->getMock('EntityManager', array('persist', 'flush', 'find','getClassMetaData','getRepository'));
$this->em
->expects($this->any())
->method('persist')
->will($this->returnValue(true));
$this->em
->expects($this->any())
->method('flush')
->will($this->returnValue(true));
$this->em
->expects($this->any())
->method('find')
->will($this->returnValue(true));
$this->em
->expects($this->any())
->method('getRepository')
->will($this->returnValue(true));
$this->em
->expects($this->any())
->method('getClassMetadata')
->will($this->returnValue((object)array('name' => 'aClass')));
$this->doctrine = $this->getMock('Doctrine', array('getEntityManager'));
$this->doctrine
->expects($this->any())
->method('getEntityManager')
->will($this->returnValue($this->em));
$this->setApplicationConfig(
include __DIR__ . '../../../../../../config/application.config.php'
);
parent::setUp();
}
public function testIndexActionCanBeAccessed()
{
//$albumTableMock = $this->getMockBuilder('Album\Models\AlbumTable')
$albumTableMock = $this->getMockBuilder('Album\Entity\Album')
->disableOriginalConstructor()
->getMock();
$albumTableMock->expects($this->once())
//->method('fetchAll')
->method('findAll')
->will($this->returnValue(array()));
$serviceManager = $this->getApplicationServiceLocator();
$serviceManager->setAllowOverride(true);
$serviceManager->setService('Album\Entity\Album', $albumTableMock);
$this->dispatch('/album');
$this->assertResponseStatusCode(200);
$this->assertModuleName('Album');
$this->assertControllerName('Album\Controller\Album');
$this->assertControllerClass('AlbumController');
$this->assertMatchedRouteName('album');
}
I think I've set up the basics of an EntityManager mock although the references I based this off of had mocked a FakeRepository() object that they returned the the getRepository() method that wasn't shown. Also, I'm not sure how to call the mocked EntityManager in the test in place of the original getMock call.
You need to use GenericTestsDatabaseTestCase for database test cases and need to provide mysql dump file in xml format as dataset in database test case class. Due to this, during unit testing, application will not query on actual database, instead it'll query on mysql xml dump. Other things like Exception/Error in doctrine entity if there is not any matched columns etc.. will work as it is. In short, due to dataset [database xml dump file] doctine will fire query on xml dataset file instead of actual database.