ZF3 Unit test authentication onBootstrap

主宰稳场 提交于 2020-03-02 04:58:35

问题


I have a problem getting a unit test to run for my IndexController class.

The unit test just does the following (inspired from the unit-test tutorial of zf3):

IndexControllerTest.php:

public function testIndexActionCanBeAccessed()
{
    $this->dispatch('/', 'GET');
    $this->assertResponseStatusCode(200);
    $this->assertModuleName('main');
    $this->assertControllerName(IndexController::class); // as specified in router's controller name alias
    $this->assertControllerClass('IndexController');
    $this->assertMatchedRouteName('main');
}

In the Module.php I've some functionality to check if there is a user logged in, else he will be redirected to a login route.

Module.php:

public function onBootstrap(MvcEvent $mvcEvent)
{
    /** @var AuthService $authService */
    $authService = $mvcEvent->getApplication()->getServiceManager()->get(AuthService::class);
    $this->auth = $authService->getAuth(); // returns the Zend AuthenticationService object

    // store user and role in global viewmodel
    if ($this->auth->hasIdentity()) {
        $curUser = $this->auth->getIdentity();
        $mvcEvent->getViewModel()->setVariable('curUser', $curUser['system_name']);
        $mvcEvent->getViewModel()->setVariable('role', $curUser['role']);
        $mvcEvent->getApplication()->getEventManager()->attach(MvcEvent::EVENT_ROUTE, [$this, 'checkPermission']);
    } else {
        $mvcEvent->getApplication()->getEventManager()->attach(MvcEvent::EVENT_DISPATCH, [$this, 'authRedirect'], 1000);
    }
}

The checkPermission method just checks if the user role and the matched route are in the acl storage. If this fails I will redirect a status code of 404.

Problem: The unit test fails: "Failed asserting response code "200", actual status code is "302" Therefore the unit test jumps into the else case from my onBootstrap method in the Module.php where the redirect happen.

I did the following setUp in the TestCase but it doesn't work:

public function setUp()
{
    // override default configuration values
    $configOverrides = [];

    $this->setApplicationConfig(ArrayUtils::merge(
        include __DIR__ . '/../../../../config/application.config.php',
        $configOverrides
    ));

    $user = new Employee();
    $user->id = 1;
    $user->system_name = 'admin';
    $user->role = 'Admin';

    $this->authService = $this->prophesize(AuthService::class);
    $auth = $this->prophesize(AuthenticationService::class);
    $auth->hasIdentity()->willReturn(true);
    $auth->getIdentity()->willReturn($user);

    $this->authService->getAuth()->willReturn($auth->reveal());

    $this->getApplicationServiceLocator()->setAllowOverride(true);
    $this->getApplicationServiceLocator()->setService(AuthService::class, $this->authService->reveal());
    $this->getApplicationServiceLocator()->setAllowOverride(false);

    parent::setUp();
}

Hints are very appreciated

The code might differ a bit from Zend Framework 2 but If you have a simple working example in zf2 maybe I can transform it into zf3 style.

I don't use ZfcUser - just the zend-acl / zend-authentication stuff


回答1:


After several days of headache I've got a working solution.

First I moved all the code within the onBootstrap to a Listener, because the phpunit mocks are generated after the zf bootstrapping and therefore are non existent in my unit tests.

The key is, that the services are generated in my callable listener method, which is called after zf finished bootstrapping. Then PHPUnit can override the service with the provided mock.

AuthenticationListener

class AuthenticationListener implements ListenerAggregateInterface
{
use ListenerAggregateTrait;

/**
 * @var AuthenticationService
 */
private $auth;

/**
 * @var Acl
 */
private $acl;

/**
 * Attach one or more listeners
 *
 * Implementors may add an optional $priority argument; the EventManager
 * implementation will pass this to the aggregate.
 *
 * @param EventManagerInterface $events
 * @param int $priority
 *
 * @return void
 */
public function attach(EventManagerInterface $events, $priority = 1)
{
    $this->listeners[] = $events->attach(MvcEvent::EVENT_ROUTE, [$this, 'checkAuthentication']);
}

/**
 * @param MvcEvent $event
 */
public function checkAuthentication($event)
{
    $this->auth = $event->getApplication()->getServiceManager()->get(AuthenticationService::class);
    $aclService = $event->getApplication()->getServiceManager()->get(AclService::class);
    $this->acl = $aclService->init();

    $event->getViewModel()->setVariable('acl', $this->acl);
    if ($this->auth->hasIdentity()) {
        $this->checkPermission($event);
    } else {
        $this->authRedirect($event);
    }
}

// checkPermission & authRedirect method
}

Now my onBootstrap got really small, just like ZF wants it. documentation reference

Module.php

public function onBootstrap(MvcEvent $event)
{
    $authListener = new AuthenticationListener();
    $authListener->attach($event->getApplication()->getEventManager());
}

Finally my mocking in the unit test looks like this:

IndexControllerTest

private function authMock()
{
    $mockAuth = $this->getMockBuilder(AuthenticationService::class)->disableOriginalConstructor()->getMock();
    $mockAuth->expects($this->any())->method('hasIdentity')->willReturn(true);
    $mockAuth->expects($this->any())->method('getIdentity')->willReturn(['id' => 1, 'systemName' => 'admin', 'role' => 'Admin']);

    $this->getApplicationServiceLocator()->setAllowOverride(true);
    $this->getApplicationServiceLocator()->setService(AuthenticationService::class, $mockAuth);
    $this->getApplicationServiceLocator()->setAllowOverride(false);
}


来源:https://stackoverflow.com/questions/39075255/zf3-unit-test-authentication-onbootstrap

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!