• Willkommen im Zend Framework Forum

    ZF1 Zend Framework 1 + ZF2 Zend Framework 2

    Das Zend Framework Forum ist seit 2006 die erste Anlaufstelle für Zend Framework Entwickler in Deutschland. Mit über 70.000 Beiträgen und einer steigenden Nutzerzahl bietet das Forum hilfreiche Themen und ZF-Tutorials für professionelle Entwickler, fortgeschrittene Programmierer sowie Zend Framework Einsteiger.
    Wenn dies Dein erster Besuch in der Zend Framework Community ist, lies bitte zuerst die Hilfe - FAQ durch. Du musst Dich registrieren, bevor Du Beiträge verfassen kannst. Klicke oben auf 'Registrieren', um die Registrierung zu starten. Du kannst auch jetzt schon Beiträge lesen. Hier im Forum findest Du die Zend Framework Hilfe, die Du suchst!

    Grüße an alle Zend Framework Entwickler. Das Team vom Zend Framework Forum!

    Drupal Agentur

ZF2 PHPUnit PluginMock in Controller wird nicht gesetzt

Hiho,

nach vielen Stunden des Mockens und testens muss ich jetzt dochmal fragen.
Ich habe in meinem Projekt Controller die ein Plugin benutzen (User-Plugin, hat die Identity und so weiter).
In meinen Tests fällt mir dieses Plugin nun auf die Füße.
Das ist der Test der fehlschlägt:
PHP:
public function testIndexActionCanBeAccessed()
{    $this->dispatch('/statistic');
    $this->assertResponseStatusCode(200);
    $this->assertModuleName('Statistic');
    $this->assertControllerName('StatisticController');
    $this->assertControllerClass('StatisticController');
    $this->assertMatchedRouteName('statistic');
}
Der Fehler liegt daran, das das Plugin nicht ersetzt wird. Somit enthält $this->User() immer das echte Plugin statt mein Mock, das gemockte Plugin wird hier gesetzt:
PHP:
private function setUserPlugin()
{
    $authMock = self::getMockBuilder('Zend\Authentication\AuthenticationService')
        ->disableOriginalConstructor()
        ->setMethods(array('hasIdentity', 'getIdentity'))
        ->getMock();

    $authMock->expects(self::once())
        ->method('hasIdentity')
        ->will(self::returnValue(true));

    $authMock->expects(self::once())
        ->method('getIdentity')
        ->will(self::returnValue(array('userid' => 1)));
    $sm = \StatisticTest\Bootstrap::getServiceManager();
    $sm->setAllowOverride(true);
    $sm->setService('auth_service', $authMock);

    $this->setApplicationMapper();

    $pluginMock = self::getMockBuilder('Application\Controller\Plugin\UserPlugin')
        ->setMethods(array('__invoke', 'hasIdentity', 'getIdentity', 'getAuthService','setController', 'getController'))
        ->getMock();
    $pluginMock->expects(self::once())
        ->method('__invoke')
        ->will(self::returnValue($this->getUserMock()));
    $pluginMock->expects(self::once())
        ->method('hasIdentity')
        ->will(self::returnValue(true));
    $pluginMock->expects(self::once())
        ->method('getIdentity')
        ->will(self::returnValue(array('userid' => 1)));

    $sm->setAllowOverride(true);
    $sm->get('ControllerPluginManager')->setService('User', $pluginMock);
    $sm->setAllowOverride(false);
}
Die funktion "setUserPlugin" wird im setUp des Tests ausgeführt:
PHP:
public function setUp()
{
    $this->setUserPlugin();
    $this->setServiceMock();

    $this->controller = new \Statistic\Controller\StatisticController();
    parent::setUp();
}
Da ich nicht weiter weis bitte ich euch da mal um Hilfe.
Im Moment hab ich 2 Gedanken. Entweder wird das Plugin einfach nie gesetzt, was wahrscheinlich ist da alle Testausgaben im "echten" Plugin von PHPUnit ausgeführt werden oder ich hab irgendwas entscheidendes übersehen.
Bin euch auf jedenfall dankbar für jeden Ansatzpunkt
 

Kaiuwe

Super-Moderator
…oder ich hab irgendwas entscheidendes übersehen.
Mir kommt das alles viel zu kompliziert vor, einiges ist auch unklar und anderes macht mir Angst. ;)

PHP:
$sm = \StatisticTest\Bootstrap::getServiceManager();
Was ist das?

PHP:
$pluginMock = self::getMockBuilder('Application\Controller\Plugin\UserPlugin')
Warum ist dies notwendig?

PHP:
$this->setServiceMock();
Was ist das?


Um es mal kurz zu sagen: „zend-test“ initialisiert deine komplette Anwendung und damit auch dein Kontroller-Plugin. Wenn du jetzt den Status „eingeloggt“ durchspielen möchtest, dann reicht es doch aus, im Service-Manager den „Authentication-Service“ (AuthenticationServiceInterface) zu hinterlegen / überschreiben.

PHP:
$this->getApplicationServiceLocator()->setAllowOverride(true);
$this->getApplicationServiceLocator()->setService(
    \Zend\Authentication\AuthenticationServiceInterface::class,
    $myAuthenticationServiceMockObject
);
$this->getApplicationServiceLocator()->setAllowOverride(false);
Das Ganze kannst du als „Trait“ umsetzen und so in deinen Testklassen wiederverwenden. (etwas Hintergrund zur Thematik Traits in Tests)
 
Um deine Fragen zu beantworten:
Was ist das?
$sm = \StatisticTest\Bootstrap::getServiceManager();
Den ServiceManager aus der TestBootstrap laden. wird zum überschreiben von Services und Plugins genutzt.

Warum ist dies notwendig?
$pluginMock = self::getMockBuilder('Application\Controller\Plugin\UserPlugin')
Erstellt ein Mock des UserPlugins (das was ich in den ServiceManager schreiben will).

Warum ist dies notwendig?
$this->setServiceMock();

Setzt noch ein weiteres MockObjeckt ;)

Was ist das?

Config 'auth_service' heist der Service bei mir, ist noch etwas älter und wird nicht per Factory erstellt aber darum geht es ja nicht (vor dem Refactorn will ich erstmal Tests schreiben).
Im Endeffekt möchte ich sämtliche Abhängigkeiten der zu testenden Klasse mocken. Denn, ich will NUR diese Klasse testen und nicht noch anderen Code.
Daher auch die lustigen kleinen MockObjekte und MockBuilder.
 
Zuletzt bearbeitet:

Kaiuwe

Super-Moderator
Im Endeffekt möchte ich sämtliche Abhängigkeiten der zu testenden Klasse mocken. Denn, ich will NUR diese Klasse testen und nicht noch anderen Code.
Bitte verwechsle nicht die Unit-Tests mit den Integrationstests:

  1. Wie in deinem Ausgangsbeitrag zu entnehmen ist, verwendest du „zend-test“ und dies ist zum Testen deiner MVC-Anwendung. Also genau das Zusammenspiel der Klassen und Komponenten.
  2. Um Klassen einzelnen zu testen bleibt es beim „einfachen“ PHPUnit und hier musst du die Test-Double erstellen.
Du wirst gerade beides durcheinander!
 

Kaiuwe

Super-Moderator
Den ServiceManager aus der TestBootstrap laden. wird zum überschreiben von Services und Plugins genutzt.
Für die Verwendung von „zend-test“ wird keine Bootstrap-Datei oder Klasse benötigt. Siehe dazu in der Doku.
Den Service-Manager bekommst in deiner Testklasse über die Methode „getApplicationServiceLocator“.

Erstellt ein Mock des UserPlugins (das was ich in den ServiceManager schreiben will).
Bei Integrationstests nicht überschreiben, denn das Plugin soll innerhalb der Anwendung korrekt funktionieren.
Den Test für die einzelne Klasse solltest du bereits in einem Unit-Test abgewickelt haben.
 
Es handelt sich bei dem Test um einen UnitTest für den Controller, keinen Integrationstest. Hab ich auch nirgends geschrieben.
Sonst würde ich mir nciht die Mühe des Mockens machen.
Hab meinen Fehler auch gefunden, im Controller war der ServiceLocator nicht gefüllt.
 

Kaiuwe

Super-Moderator
Es handelt sich bei dem Test um einen UnitTest für den Controller, keinen Integrationstest. Hab ich auch nirgends geschrieben.
Dein Ausgangsbeitrag sagt aber etwas anders:
PHP:
public function testIndexActionCanBeAccessed()
{    $this->dispatch('/statistic');
    $this->assertResponseStatusCode(200);
    $this->assertModuleName('Statistic');
    $this->assertControllerName('StatisticController');
    $this->assertControllerClass('StatisticController');
    $this->assertMatchedRouteName('statistic');
}
Daher schloss ich darauf, dass es um Integrationstests geht.
 
Wenn man sich das stink normale Tutorial von ZF 2 anschaut ist das ein normaler Controllertest, der prüft ob die Action erreichbar ist. Gut kann man auch schon zu den Integrationstests zählen, ist aber sicher Ansichtssache.
 
Oben