Unit test failing for the paginated module

I was trying to follow this tutorial with the pagination + unit test , it is failing and not sure how to fix it. Can any one help me . thanks in advance

AlbumController.php

<?php

namespace Album\Controller;

use Album\Model\AlbumTable;
use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\View\Model\ViewModel;
use Album\Form\AlbumForm;
use Album\Model\Album;

class AlbumController extends AbstractActionController
{
  // Add this property:
    private $table;

  // Add this constructor:
    public function __construct(AlbumTable $table)
    {
        $this->table = $table;
    }

    public function indexAction()
    {
      // Grab the paginator from the AlbumTable:
        $paginator = $this->table->fetchAll(true);

      // Set the current page to what has been passed in query string,
      // or to 1 if none is set, or the page is invalid:
        $page = (int) $this->params()->fromQuery('page', 1);
        $page = ($page < 1) ? 1 : $page;
        $paginator->setCurrentPageNumber($page);

      // Set the number of items per page to 10:
        $paginator->setItemCountPerPage(10);

        return new ViewModel(['paginator' => $paginator]);
    }

    public function addAction()
    {
        $form = new AlbumForm();
        $form->get('submit')->setValue('Add');

        $request = $this->getRequest();

        if (! $request->isPost()) {
            return ['form' => $form];
        }

        $album = new Album();
        $form->setInputFilter($album->getInputFilter());
        $form->setData($request->getPost());

        if (! $form->isValid()) {
            return ['form' => $form];
        }

        $album->exchangeArray($form->getData());
        $this->table->saveAlbum($album);
        return $this->redirect()->toRoute('album');
    }

    public function editAction()
    {
        $id = (int) $this->params()->fromRoute('id', 0);

        if (0 === $id) {
            return $this->redirect()->toRoute('album', ['action' => 'add']);
        }

      // Retrieve the album with the specified id. Doing so raises
      // an exception if the album is not found, which should result
      // in redirecting to the landing page.
        try {
            $album = $this->table->getAlbum($id);
        } catch (\Exception $e) {
            return $this->redirect()->toRoute('album', ['action' => 'index']);
        }

        $form = new AlbumForm();
        $form->bind($album);
        $form->get('submit')->setAttribute('value', 'Edit');

        $request = $this->getRequest();
        $viewData = ['id' => $id, 'form' => $form];

        if (! $request->isPost()) {
            return $viewData;
        }

        $form->setInputFilter($album->getInputFilter());
        $form->setData($request->getPost());

        if (! $form->isValid()) {
            return $viewData;
        }

        try {
            $this->table->saveAlbum($album);
        } catch (\Exception $e) {
        }

      // Redirect to album list
        return $this->redirect()->toRoute('album', ['action' => 'index']);
    }

    public function deleteAction()
    {
        $id = (int) $this->params()->fromRoute('id', 0);
        if (! $id) {
            return $this->redirect()->toRoute('album');
        }

        $request = $this->getRequest();
        if ($request->isPost()) {
            $del = $request->getPost('del', 'No');

            if ($del == 'Yes') {
                $id = (int) $request->getPost('id');
                $this->table->deleteAlbum($id);
            }

          // Redirect to list of albums
            return $this->redirect()->toRoute('album');
        }

        return [
        'id'    => $id,
        'album' => $this->table->getAlbum($id),
        ];
    }
}

AlbumControllerTest.php


<?php

namespace AlbumTest\Controller;

use Album\Controller\AlbumController;
use Album\Model\Album;
use Album\Model\AlbumTable;
use Laminas\ServiceManager\ServiceManager;
use Laminas\Stdlib\ArrayUtils;
use Laminas\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;

class AlbumControllerTest extends AbstractHttpControllerTestCase
{
    use ProphecyTrait;

    protected $traceError = true;

    protected $albumTable;

    protected function setUp(): void
    {
      // The module configuration should still be applicable for tests.
      // You can override configuration here with test case specific values,
      // such as sample view templates, path stacks, module_listener_options,
      // etc.
        $configOverrides = [];

        $applicationConfigPath = realpath(__DIR__ . '/../../../../config/application.config.php');

        $this->setApplicationConfig(ArrayUtils::merge(// Grabbing the full application configuration:
            include $applicationConfigPath,
            $configOverrides
        ));
        parent::setUp();

        $this->configureServiceManager($this->getApplicationServiceLocator());
    }

    public function testIndexActionCanBeAccessed()
    {

        $this->albumTable->fetchAll()->willReturn([]);

        $this->dispatch('/album');
        $this->assertResponseStatusCode(200);
        $this->assertModuleName('Album');
        $this->assertControllerName(AlbumController::class);
        $this->assertControllerClass('AlbumController');
        $this->assertMatchedRouteName('album');
    }

    public function TestIndexActionCanNotBeAccessedDB()
    {
        $services = $this->getApplicationServiceLocator();
        $config = $services->get('config');
        unset($config['db']);
        $services->setAllowOverride(true);
        $services->setService('config', $config);
        $services->setAllowOverride(false);

        $this->dispatch('/album');
        $this->assertResponseStatusCode(200);
        $this->assertModuleName('Album');
        $this->assertControllerName(AlbumController::class);
        $this->assertControllerClass('AlbumController');
        $this->assertMatchedRouteName('album');
    }

    protected function configureServiceManager(ServiceManager $services)
    {
        $services->setAllowOverride(true);

        $services->setService('config', $this->updateConfig($services->get('config')));
        $services->setService(AlbumTable::class, $this->mockAlbumTable()->reveal());

        $services->setAllowOverride(false);
    }

    protected function updateConfig($config)
    {
        $config['db'] = [];
        return $config;
    }

    protected function mockAlbumTable()
    {
        $this->albumTable = $this->prophesize(AlbumTable::class);
        return $this->albumTable;
    }


    public function testAddActionRedirectsAfterValidPost()
    {
        $this->albumTable
        ->saveAlbum(Argument::type(Album::class))
        ->shouldBeCalled();

        $postData = [
        'title'  => 'Led Zeppelin III',
        'artist' => 'Led Zeppelin',
        'id'     => '',
        ];
        $this->dispatch('/album/add', 'POST', $postData);
        $this->assertResponseStatusCode(302);
        $this->assertRedirectTo('/album');
    }
}


Module.php

<?php

namespace Album;

use Laminas\Db\Adapter\AdapterInterface;
use Laminas\Db\ResultSet\ResultSet;
use Laminas\ModuleManager\Feature\ConfigProviderInterface;
use Laminas\Db\TableGateway\TableGateway;

class Module implements ConfigProviderInterface {

  public function getConfig() {
    return include __DIR__ . '/../config/module.config.php';
  }

  // Add this method:
  public function getServiceConfig() {
    return [
      'factories' => [
        Model\AlbumTable::class        => function($container) {
          $tableGateway = $container->get(Model\AlbumTableGateway::class);

          return new Model\AlbumTable($tableGateway);
        },
        Model\AlbumTableGateway::class => function($container) {
          $dbAdapter          = $container->get(AdapterInterface::class);
          $resultSetPrototype = new ResultSet();
          $resultSetPrototype->setArrayObjectPrototype(new Model\Album());

          return new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
        },
      ],
    ];
  }

  public function getControllerConfig() {
    return [
      'factories' => [
        Controller\AlbumController::class => function($container) {
          return new Controller\AlbumController($container->get(Model\AlbumTable::class));
        },
      ],
    ];
  }
}

This code is perfectly working fine while I run through the browser and navigate to /album and all the operation is working perfect like add. edit and delete and pagination as well but it fails for the unit test caese

The current result is correct, because if the paginator is added, the tests can no longer be run as before.

Because the AlbumTable class no longer returns an array here:

public function testIndexActionCanBeAccessed()
{
    $this->albumTable->fetchAll()->willReturn([]);

    // …
}

If the parameter for pagination is set to true, a paginator is returned:

public function fetchAll($paginated = false)
{
    if ($paginated) {
        return $this->fetchPaginatedResults();
    }

    return $this->tableGateway->select();
}

private function fetchPaginatedResults()
{
    // …

    return new Paginator($paginatorAdapter);
}

This also needs to be changed in your tests. The ArrayAdapter of laminas-paginator can help in tests.

References:

Here is :- AlbumTable.php code

I also followed this article . Adding laminas-paginator to the Album Module - tutorials - Laminas Docs for the pagination album module

<?php

namespace Album\Model;

use RuntimeException;
use Laminas\Db\TableGateway\TableGatewayInterface;
use Laminas\Db\ResultSet\ResultSet;
use Laminas\Db\Sql\Select;
use Laminas\Paginator\Adapter\DbSelect;
use Laminas\Paginator\Paginator;

class AlbumTable
{
    private $tableGateway;

    public function __construct(TableGatewayInterface $tableGateway)
    {
        $this->tableGateway = $tableGateway;
    }

    public function fetchAll($paginated = false)
    {
        if ($paginated) {
            return $this->fetchPaginatedResults();
        }

        return $this->tableGateway->select();
    }

    private function fetchPaginatedResults()
    {
      // Create a new Select object for the table:
        $select = new Select($this->tableGateway->getTable());

      // Create a new result set based on the Album entity:
        $resultSetPrototype = new ResultSet();
        $resultSetPrototype->setArrayObjectPrototype(new Album());

      // Create a new pagination adapter object:
        $paginatorAdapter = new DbSelect(
      // our configured select object:
            $select,
            // the adapter to run it against:
            $this->tableGateway->getAdapter(),
            // the result set to hydrate:
            $resultSetPrototype
        );

        return new Paginator($paginatorAdapter);
    }


    public function getAlbum($id)
    {
        $id     = (int)$id;
        $rowset = $this->tableGateway->select(['id' => $id]);
        $row    = $rowset->current();
        if (! $row) {
            throw new RuntimeException(sprintf(
                'Could not find row with identifier %d',
                $id
            ));
        }

        return $row;
    }

    public function saveAlbum(Album $album)
    {
        $data = [
        'artist' => $album->artist,
        'title'  => $album->title,
        ];

        $id = (int)$album->id;

        if ($id === 0) {
            $this->tableGateway->insert($data);

            return;
        }

        try {
            $this->getAlbum($id);
        } catch (RuntimeException $e) {
            throw new RuntimeException(sprintf(
                'Cannot update album with identifier %d; does not exist',
                $id
            ));
        }

        $this->tableGateway->update($data, ['id' => $id]);
    }

    public function deleteAlbum($id)
    {
        $this->tableGateway->delete(['id' => (int)$id]);
    }
}

There is another problem: you have created a method called mockAlbumTable and set the result of this method as a service for the application, but you do not use this in your tests.

Regardless of this, I recommend that you look at and use Mezzio. It is easier and more open to use.