问题
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