title: ZF2 tutorial Album PHPUnit test failing with Undefined property: AlbumTest\Model\AlbumTableTest::$controller
body: <p>I am building the ZF2 Album tutorial application and when I unit test I am consistently receiving the same error even though I've rebuilt the application again.  Can someone tell me what is going on here?  I am dumping all relevant information here to assist.  The error is:</p>
  
  <pre><code>PHPUnit 3.7.10 by Sebastian Bergmann.
  
  Configuration read from D:\PHP\zf2-tutorial\module\Album\test\phpunit.xml.dist
  
  ......E
  
  Time: 0 seconds, Memory: 6.25Mb
  
  There was 1 error:
  
  1) AlbumTest\Model\AlbumTableTest::testGetAlbumTableReturnsAnInstanceOfAlbumTable
  Undefined property: AlbumTest\Model\AlbumTableTest::$controller
  
  D:\PHP\zf2-tutorial\module\Album\test\AlbumTest\Model\AlbumTableTest.php:116
  
  FAILURES!
  Tests: 7, Assertions: 9, Errors: 1.
  </code></pre>
  
  <p>AlbumTableTest.php is as follows and the error is received on the final assertion:</p>
  
  <pre><code>&lt;?php
  
  namespace AlbumTest\Model;
  
  use Album\Model\AlbumTable;
  use Album\Model\Album;
  use Zend\Db\ResultSet\ResultSet;
  use PHPUnit_Framework_TestCase;
  
  class AlbumTableTest extends PHPUnit_Framework_TestCase {
  
      public function testFetchAllReturnsAllAlbums() {
          $resultSet = new ResultSet();
          $mockTableGateway = $this-&gt;getMock('Zend\Db\TableGateway\TableGateway', array('select'), array(), '', false);
          $mockTableGateway-&gt;expects($this-&gt;once())
                  -&gt;method('select')
                  -&gt;with()
                  -&gt;will($this-&gt;returnValue($resultSet));
  
          $albumTable = new AlbumTable($mockTableGateway);
  
          $this-&gt;assertSame($resultSet, $albumTable-&gt;fetchAll());
      }
  
      public function testCanRetrieveAnAlbumByItsId() {
          $album = new Album();
          $album-&gt;exchangeArray(array('id' =&gt; 123,
              'artist' =&gt; 'The Military Wives',
              'title' =&gt; 'In My Dreams'));
  
          $resultSet = new ResultSet();
          $resultSet-&gt;setArrayObjectPrototype(new Album());
          $resultSet-&gt;initialize(array($album));
  
          $mockTableGateway = $this-&gt;getMock('Zend\Db\TableGateway\TableGateway', array('select'), array(), '', false);
          $mockTableGateway-&gt;expects($this-&gt;once())
                  -&gt;method('select')
                  -&gt;with(array('id' =&gt; 123))
                  -&gt;will($this-&gt;returnValue($resultSet));
  
          $albumTable = new AlbumTable($mockTableGateway);
  
          $this-&gt;assertSame($album, $albumTable-&gt;getAlbum(123));
      }
  
      public function testCanDeleteAnAlbumByItsId() {
          $mockTableGateway = $this-&gt;getMock('Zend\Db\TableGateway\TableGateway', array('delete'), array(), '', false);
          $mockTableGateway-&gt;expects($this-&gt;once())
                  -&gt;method('delete')
                  -&gt;with(array('id' =&gt; 123));
  
          $albumTable = new AlbumTable($mockTableGateway);
          $albumTable-&gt;deleteAlbum(123);
      }
  
      public function testSaveAlbumWillInsertNewAlbumsIfTheyDontAlreadyHaveAnId() {
          $albumData = array('artist' =&gt; 'The Military Wives', 'title' =&gt; 'In My Dreams');
          $album = new Album();
          $album-&gt;exchangeArray($albumData);
  
          $mockTableGateway = $this-&gt;getMock('Zend\Db\TableGateway\TableGateway', array('insert'), array(), '', false);
          $mockTableGateway-&gt;expects($this-&gt;once())
                  -&gt;method('insert')
                  -&gt;with($albumData);
  
          $albumTable = new AlbumTable($mockTableGateway);
          $albumTable-&gt;saveAlbum($album);
      }
  
      public function testSaveAlbumWillUpdateExistingAlbumsIfTheyAlreadyHaveAnId() {
          $albumData = array('id' =&gt; 123, 'artist' =&gt; 'The Military Wives', 'title' =&gt; 'In My Dreams');
          $album = new Album();
          $album-&gt;exchangeArray($albumData);
  
          $resultSet = new ResultSet();
          $resultSet-&gt;setArrayObjectPrototype(new Album());
          $resultSet-&gt;initialize(array($album));
  
          $mockTableGateway = $this-&gt;getMock('Zend\Db\TableGateway\TableGateway', array('select', 'update'), array(), '', false);
          $mockTableGateway-&gt;expects($this-&gt;once())
                  -&gt;method('select')
                  -&gt;with(array('id' =&gt; 123))
                  -&gt;will($this-&gt;returnValue($resultSet));
          $mockTableGateway-&gt;expects($this-&gt;once())
                  -&gt;method('update')
                  -&gt;with(array('artist' =&gt; 'The Military Wives', 'title' =&gt; 'In My Dreams'), array('id' =&gt; 123));
  
          $albumTable = new AlbumTable($mockTableGateway);
          $albumTable-&gt;saveAlbum($album);
      }
  
      public function testExceptionIsThrownWhenGettingNonexistentAlbum() {
          $resultSet = new ResultSet();
          $resultSet-&gt;setArrayObjectPrototype(new Album());
          $resultSet-&gt;initialize(array());
  
          $mockTableGateway = $this-&gt;getMock('Zend\Db\TableGateway\TableGateway', array('select'), array(), '', false);
          $mockTableGateway-&gt;expects($this-&gt;once())
                  -&gt;method('select')
                  -&gt;with(array('id' =&gt; 123))
                  -&gt;will($this-&gt;returnValue($resultSet));
  
          $albumTable = new AlbumTable($mockTableGateway);
  
          try {
              $albumTable-&gt;getAlbum(123);
          } catch (\Exception $e) {
              $this-&gt;assertSame('Could not find row 123', $e-&gt;getMessage());
              return;
          }
  
          $this-&gt;fail('Expected exception was not thrown');
      }
  
      public function testGetAlbumTableReturnsAnInstanceOfAlbumTable() {
          $this-&gt;assertInstanceOf('Album\Model\AlbumTable', $this-&gt;controller-&gt;getAlbumTable());
      }
  
  }
  
  ?&gt;
  </code></pre>
  
  <p>getAlbumTable is in AlbumController as follows: </p>
  
  <pre><code>&lt;?php
  
  namespace Album\Controller;
  
  use Zend\Mvc\Controller\AbstractActionController;
  use Zend\View\Model\ViewModel;
  
  class AlbumController extends AbstractActionController {
  
      protected $albumTable;
  
      public function indexAction() {
          return new ViewModel(array(
                      'albums' =&gt; $this-&gt;getAlbumTable()-&gt;fetchAll(),
                  ));
      }
  
      public function addAction() {
  
      }
  
      public function editAction() {
  
      }
  
      public function deleteAction() {
  
      }
  
      public function getAlbumTable() {
          if (!$this-&gt;albumTable) {
              $sm = $this-&gt;getServiceLocator();
              $this-&gt;albumTable = $sm-&gt;get('Album\Model\AlbumTable');
          }
          return $this-&gt;albumTable;
      }
  
  }
  
  ?&gt;
  </code></pre>
  
  <p>And AlbumTable is:</p>
  
  <pre><code>&lt;?php
  namespace Album\Model;
  
  use Zend\Db\TableGateway\TableGateway;
  
  class AlbumTable
  {
      protected $tableGateway;
  
      public function __construct(TableGateway $tableGateway)
      {
          $this-&gt;tableGateway = $tableGateway;
      }
  
      public function fetchAll()
      {
          $resultSet = $this-&gt;tableGateway-&gt;select();
          return $resultSet;
      }
  
      public function getAlbum($id)
      {
          $id  = (int) $id;
          $rowset = $this-&gt;tableGateway-&gt;select(array('id' =&gt; $id));
          $row = $rowset-&gt;current();
          if (!$row) {
              throw new \Exception("Could not find row $id");
          }
          return $row;
      }
  
      public function saveAlbum(Album $album)
      {
          $data = array(
              'artist' =&gt; $album-&gt;artist,
              'title'  =&gt; $album-&gt;title,
          );
  
          $id = (int)$album-&gt;id;
          if ($id == 0) {
              $this-&gt;tableGateway-&gt;insert($data);
          } else {
              if ($this-&gt;getAlbum($id)) {
                  $this-&gt;tableGateway-&gt;update($data, array('id' =&gt; $id));
              } else {
                  throw new \Exception('Form id does not exist');
              }
          }
      }
  
      public function deleteAlbum($id)
      {
          $this-&gt;tableGateway-&gt;delete(array('id' =&gt; $id));
      }
  }
  ?&gt;
  </code></pre>
  
  <p>Module.php is:</p>
  
  <pre><code>&lt;?php
  namespace Album;
  
  class Module
  {
      public function getAutoloaderConfig()
      {
          return array(
              'Zend\Loader\ClassMapAutoloader' =&gt; array(
                  __DIR__ . '/autoload_classmap.php',
              ),
              'Zend\Loader\StandardAutoloader' =&gt; array(
                  'namespaces' =&gt; array(
                      __NAMESPACE__ =&gt; __DIR__ . '/src/' . __NAMESPACE__,
                  ),
              ),
          );
      }
  
      public function getConfig()
      {
          return include __DIR__ . '/config/module.config.php';
      }
      // Add this method:
      public function getServiceConfig()
      {
          return array(
              'factories' =&gt; array(
                  'Album\Model\AlbumTable' =&gt;  function($sm) {
                      $tableGateway = $sm-&gt;get('AlbumTableGateway');
                      $table = new AlbumTable($tableGateway);
                      return $table;
                  },
                  'AlbumTableGateway' =&gt; function ($sm) {
                      $dbAdapter = $sm-&gt;get('Zend\Db\Adapter\Adapter');
                      $resultSetPrototype = new ResultSet();
                      $resultSetPrototype-&gt;setArrayObjectPrototype(new Album());
                      return new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
                  },
              ),
          );
      }
  }
  ?&gt;
  </code></pre>
  
  <p>module.config.php is:</p>
  
  <pre><code>&lt;?php
  return array(
      'controllers' =&gt; array(
          'invokables' =&gt; array(
              'Album\Controller\Album' =&gt; 'Album\Controller\AlbumController',
          ),
      ),
  
      // The following section is new and should be added to your file
      'router' =&gt; array(
          'routes' =&gt; array(
              'album' =&gt; array(
                  'type'    =&gt; 'segment',
                  'options' =&gt; array(
                      'route'    =&gt; '/album[/:action][/:id]',
                      'constraints' =&gt; array(
                          'action' =&gt; '[a-zA-Z][a-zA-Z0-9_-]*',
                          'id'     =&gt; '[0-9]+',
                      ),
                      'defaults' =&gt; array(
                          'controller' =&gt; 'Album\Controller\Album',
                          'action'     =&gt; 'index',
                      ),
                  ),
              ),
          ),
      ),
  
      'view_manager' =&gt; array(
          'template_path_stack' =&gt; array(
              'album' =&gt; __DIR__ . '/../view',
          ),
      ),
  );
  ?&gt;
  </code></pre>
  
  <p>application.config.php is:</p>
  
  <pre><code>&lt;?php
  return array(
      'modules' =&gt; array(
          'Application',
          'Album',                  // &lt;-- Add this line
      ),
      'module_listener_options' =&gt; array(
          'config_glob_paths'    =&gt; array(
              'config/autoload/{,*.}{global,local}.php',
          ),
          'module_paths' =&gt; array(
              './module',
              './vendor',
          ),
      ),
  );
  ?&gt;
  </code></pre>
  
  <p>Bootstrap.php:</p>
  
  <pre><code>&lt;?php
  namespace AlbumTest;//Change this namespace for your test
  
  use Zend\Loader\AutoloaderFactory;
  use Zend\Mvc\Service\ServiceManagerConfig;
  use Zend\ServiceManager\ServiceManager;
  use Zend\Stdlib\ArrayUtils;
  use RuntimeException;
  
  error_reporting(E_ALL | E_STRICT);
  chdir(__DIR__);
  
  class Bootstrap
  {
      protected static $serviceManager;
      protected static $config;
      protected static $bootstrap;
  
      public static function init()
      {
          // Load the user-defined test configuration file, if it exists; otherwise, load
          if (is_readable(__DIR__ . '/TestConfig.php')) {
              $testConfig = include __DIR__ . '/TestConfig.php';
          } else {
              $testConfig = include __DIR__ . '/TestConfig.php.dist';
          }
  
          $zf2ModulePaths = array();
  
          if (isset($testConfig['module_listener_options']['module_paths'])) {
              $modulePaths = $testConfig['module_listener_options']['module_paths'];
              foreach ($modulePaths as $modulePath) {
                  if (($path = static::findParentPath($modulePath)) ) {
                      $zf2ModulePaths[] = $path;
                  }
              }
          }
  
          $zf2ModulePaths  = implode(PATH_SEPARATOR, $zf2ModulePaths) . PATH_SEPARATOR;
          $zf2ModulePaths .= getenv('ZF2_MODULES_TEST_PATHS') ?: (defined('ZF2_MODULES_TEST_PATHS') ? ZF2_MODULES_TEST_PATHS : '');
  
          static::initAutoloader();
  
          // use ModuleManager to load this module and it's dependencies
          $baseConfig = array(
              'module_listener_options' =&gt; array(
                  'module_paths' =&gt; explode(PATH_SEPARATOR, $zf2ModulePaths),
              ),
          );
  
          $config = ArrayUtils::merge($baseConfig, $testConfig);
  
          $serviceManager = new ServiceManager(new ServiceManagerConfig());
          $serviceManager-&gt;setService('ApplicationConfig', $config);
          $serviceManager-&gt;get('ModuleManager')-&gt;loadModules();
  
          static::$serviceManager = $serviceManager;
          static::$config = $config;
      }
  
      public static function getServiceManager()
      {
          return static::$serviceManager;
      }
  
      public static function getConfig()
      {
          return static::$config;
      }
  
      protected static function initAutoloader()
      {
          $vendorPath = static::findParentPath('vendor');
  
          if (is_readable($vendorPath . '/autoload.php')) {
              $loader = include $vendorPath . '/autoload.php';
          } else {
              $zf2Path = getenv('ZF2_PATH') ?: (defined('ZF2_PATH') ? ZF2_PATH : (is_dir($vendorPath . '/ZF2/library') ? $vendorPath . '/ZF2/library' : false));
  
              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' =&gt; array(
                  'autoregister_zf' =&gt; true,
                  'namespaces' =&gt; array(
                      __NAMESPACE__ =&gt; __DIR__ . '/' . __NAMESPACE__,
                  ),
              ),
          ));
      }
  
      protected static function findParentPath($path)
      {
          $dir = __DIR__;
          $previousDir = '.';
          while (!is_dir($dir . '/' . $path)) {
              $dir = dirname($dir);
              if ($previousDir === $dir) return false;
              $previousDir = $dir;
          }
          return $dir . '/' . $path;
      }
  }
  
  Bootstrap::init();
  ?&gt;
  </code></pre>
  
  <p>TestConfig.php.dist:</p>
  
  <pre><code>&lt;?php
  return array(
      'modules' =&gt; array(
          'Album',
      ),
      'module_listener_options' =&gt; array(
          'config_glob_paths'    =&gt; array(
              '../../../config/autoload/{,*.}{global,local}.php',
          ),
          'module_paths' =&gt; array(
              'module',
              'vendor',
          ),
      ),
  );
  ?&gt;
  </code></pre>
  
  <p>And, finally, phpunit.xml.dist:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt; 
  &lt;phpunit bootstrap="Bootstrap.php"&gt; 
      &lt;testsuites&gt; 
          &lt;testsuite name="zf2tutorial"&gt; 
              &lt;directory&gt;./AlbumTest&lt;/directory&gt; 
          &lt;/testsuite&gt; 
      &lt;/testsuites&gt; 
  &lt;/phpunit&gt;
  </code></pre>