This is a question I'm often asked!  I thought I'd create a reference to help those in similar need.  Cutting straight to it, tests (examples) won't work out of the box if you are relying on things like 'params' or 'auth'.  The reason is simple, when you are testing controllers with PHPSpec, the Controller didn't go through its usual init process.  Your Controller's PluginManager as a result, won't work.

Testing controllers with PHPSpec, the Controller didn't go through its usual init process.  Your Controller's PluginManager as a result, won't work.

The knee-jerk reaction might be to try to superclass the test to initialize controllers, but this is bad behavior.  You're no longer speccing if you do this, you're actually testing integration (which goes against the doctrine your tests establish).

It's actually very simple.  Consider this method (that you can readily cut and paste into your Specs)

/**
 * Convenience method to mock plugins into controller specs
 * @param array $plugins
 * @return \Prophecy\Prophecy\ObjectProphecy
 */
private function createPluginManager($plugins = [])
{
    $prophet = new Prophet();
    $pluginManager = $prophet->prophesize(PluginManager::class);

    foreach ($plugins as $name => $plugin) {
        $pluginManager->get($name, Argument::cetera())->willReturn($plugin->getWrappedObject());
    }
    $pluginManager->setController(Argument::any())->willReturn(null);
    $this->setPluginManager($pluginManager->reveal());

    return $pluginManager;
}

Now, imagine we wanted to test a function that'd eventually look like this.

public function testAction()
{
    $vm = new ViewModel();
    if ($id = $this->params()->fromRoute('id')) {
        $vm->setVariable('id', $id);
    }
    $vm->setTerminal(true);

    return $vm;
}

You would structure your test like so:

namespace Spec\Application\Controller;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Prophecy\Prophet;
use Zend\Mvc\Controller\Plugin\Params;
use Zend\Mvc\Controller\PluginManager;
use Zend\View\Model\ViewModel;

class IndexControllerSpec extends ObjectBehavior
{
    /**
     * Convenience method to mock plugins into controller specs
     * @param array $plugins
     * @return \Prophecy\Prophecy\ObjectProphecy
     */
    private function createPluginManager($plugins = [])
    {
        $prophet = new Prophet();
        $pluginManager = $prophet->prophesize(PluginManager::class);

        foreach ($plugins as $name => $plugin) {
            $pluginManager->get($name, Argument::cetera())->willReturn($plugin->getWrappedObject());
        }
        $pluginManager->setController(Argument::any())->willReturn(null);
        $this->setPluginManager($pluginManager->reveal());
    }

    function it_can_use_params_now(Params $params)
    {
        $params->__invoke(Argument::any(), Argument::any())->willReturn($params);
        $params->fromRoute('id', Argument::any())->willReturn(1);
        $this->createPluginManager([
            'params' => $params,
        ]);

        $viewModel = $this->testAction();
        $viewModel->shouldHaveType(ViewModel::class);
        $viewModel->getVariable('id')->shouldBe(1);
    }
}

You can build on this to optimize your tests, calling the plugin creation method strategically.

 

Adjusting getRequest()

 

Now, you might be running into a circumstance where you need to modify 'getRequest' to satisfy your controller tests.  You'll have quickly noted that there is no 'setRequest', for a good reason.  If you are in a situation where this is required, here's a second helper that lets you trigger a 'fake' dispatch cycle to feed in a mocked request.  This method, as with createPluginManager above, can go into your Spec.  You could create a clever parent class that features these two if you are using them frequently.

/**
 * Simulate dispatching the request, lets you modify getRequest()'s response
 *
 * @param $request
 * @return mixed
 */
private function dispatchRequest($request)
{
    $prophet = new Prophet();
    $routeMatch = $prophet->prophesize(RouteMatch::class);
    $routeMatch->getParam('action', Argument::any())->willReturn('unused');

    $mvcEvent = $prophet->prophesize(MvcEvent::class);
    $mvcEvent->getName()->willReturn('unused');
    $mvcEvent->getRouteMatch()->willReturn($routeMatch);
    $mvcEvent->getRequest()->willReturn($request);
    $mvcEvent->setRequest(Argument::any())->willReturn($mvcEvent);
    $mvcEvent->setResponse(Argument::any())->willReturn($mvcEvent);
    $mvcEvent->setTarget(Argument::any())->willReturn($mvcEvent);
    $mvcEvent->setName(Argument::any())->willReturn($mvcEvent);
    $mvcEvent->stopPropagation(Argument::any())->willReturn($mvcEvent);
    $mvcEvent->propagationIsStopped(Argument::any())->willReturn($mvcEvent);
    $mvcEvent->setResult(Argument::any())->willReturn($mvcEvent);
    $mvcEvent->getResult()->willReturn($mvcEvent);

    $this->setEvent($mvcEvent);

    return $this->dispatch($request);
}

With that function in your spec test, and an action that looks like this:

public function testAction()
{
    $vm = new ViewModel();
    if ($id = $this->params()->fromRoute('id')) {
        $vm->setVariable('id', $id);
    }

    if( $this->getRequest()->isPost() ){
        $vm->setVariable('p', true );
    }

    $vm->setTerminal(true);

    return $vm;
}

You can then write a test that works like so:

function it_can_use_params_with_post_now(Params $params, Request $request)
{
    $params->__invoke(Argument::any(), Argument::any())->willReturn($params);
    $params->fromRoute('id', Argument::any())->willReturn(1);
    $this->createPluginManager([
        'params' => $params,
    ]);

    $request->isPost()->willReturn(true);

    $this->dispatchRequest($request);
    $viewModel = $this->testAction();
    $viewModel->shouldHaveType(ViewModel::class);
    $viewModel->getVariable('id')->shouldBe(1);
    $viewModel->getVariable('p')->shouldBe(true);
}

Hope this helps!  I'd certainly love to learn an easier way -- feel free to drop a comment below!