ZF2 unit-testing authentication
Asked Answered
L

2

23

I was learning about unit testing and I attempted to resolve the following issue:

Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for zfcUserAuthentication

... using the only answer given at:

Simple ZF2 Unit Tests for a controller using ZfcUser

So my setUp function looks the same. Unfortunately, I get the error message:

Zend\Mvc\Exception\InvalidPluginException: Plugin of type Mock_ZfcUserAuthentication_868bf824 is invalid; must implement Zend\Mvc\Controller\Plugin\PluginInterface

It is caused at this part of the code (split up in my code in the same way):

$this -> controller->getPluginManager()
->setService('zfcUserAuthentication', $authMock); // Error refers to this line.

The $authMock object is apparently not implementing plugininterface, which I need to implement to pass into setService.

Is $authMock not meant to be passed there for it's use in unit testing? Should I be using a different (unit-testing oriented) setService method?

I need a way to handle logging into my application, or my unit testing is pointless.

Thanks for any advice.

=== Edit (11/02/2013) ===

I wanted to focus on this part for clarification, as I think this is the problem area:

// Getting mock of authentication object, which is used as a plugin.
$authMock = $this->getMock('ZfcUser\Controller\Plugin\ZfcUserAuthentication');

// Some expectations of the authentication service.
$authMock   -> expects($this->any())
    -> method('hasIdentity')
    -> will($this->returnValue(true));  

$authMock   -> expects($this->any())
    -> method('getIdentity')
    -> will($this->returnValue($ZfcUserMock));

// At this point, PluginManager disallows mock being assigned as plugin because 
// it will not implement plugin interface, as mentioned.
$this -> controller->getPluginManager()
->setService('zfcUserAuthentication', $authMock);

If the mock doesn't handle necessary implementations, how else am I to pretend to login?

Longevous answered 10/2, 2013 at 19:39 Comment(3)
Am I correct it's not as necessary to unit-test controllers as it is models? I find that's where I keep all my authentication code.Longevous
I did something similar recently with no problem. What does your complete testcase class look like? Also what does your test bootstrap look like? And finally the action you're trying to test.Compline
Do you use a special application configuration when unit testing ? It is possible in that case that zfcUser module is not loaded in testing environment.Hyperostosis
D
3

You have a problem with name-spacing or your autoloader.

When you are creating your mock, the class definition of ZfcUser\Controller\Plugin\ZfcUserAuthentication is not being found. So PHPUnit creates a mock that only extends this class for your test. If the class was available then PHPUnit will use the actual class to extend when making its mock, which will then use the parent classes/interfaces.

You can see this logic here: https://github.com/sebastianbergmann/phpunit-mock-objects/blob/master/PHPUnit/Framework/MockObject/Generator.php

    if (!class_exists($mockClassName['fullClassName'], $callAutoload) &&
        !interface_exists($mockClassName['fullClassName'], $callAutoload)) {
        $prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n";

        if (!empty($mockClassName['namespaceName'])) {
            $prologue = 'namespace ' . $mockClassName['namespaceName'] .
                        " {\n\n" . $prologue . "}\n\n" .
                        "namespace {\n\n";

            $epilogue = "\n\n}";
        }

        $cloneTemplate = new Text_Template(
          $templateDir . 'mocked_clone.tpl'
        );

So if there is no class or interface, PHPUnit will actually create one itself so that the mock will meet the type hinting of original class name. However, any parent classes or interfaces will not be included because PHPUnit is not aware of them.

This would be due to not including the proper namespace in your test or having a problem in your autoloader. It is difficult to tell without actually seeing the entire test file.


Alternatively rather than mocking ZfcUser\Controller\Plugin\ZfcUserAuthentication, you could mock the Zend\Mvc\Controller\Plugin\PluginInterface in your test and pass that into the plugin manager. Though if you are type-hinting for the plugin in your code, your test still won't work.

//Mock the plugin interface for checking authorization
$authMock = $this->getMock('Zend\Mvc\Controller\Plugin\PluginInterface');

// Some expectations of the authentication service.
$authMock   -> expects($this->any())
    -> method('hasIdentity')
    -> will($this->returnValue(true));  

$authMock   -> expects($this->any())
    -> method('getIdentity')
    -> will($this->returnValue($ZfcUserMock));

$this -> controller->getPluginManager()
->setService('zfcUserAuthentication', $authMock);
Diactinic answered 15/1, 2014 at 14:54 Comment(0)
D
0

I just made an example for the FlashMessenger plugin. You should just use the ControllerPluginManager to override the ControllerPlugin. Make sure that your application bootstrap calls setApplicationConfig();

<?php
namespace SimpleTest\Controller;

use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;

class SimpleControllerTest extends AbstractHttpControllerTestCase {

  public function testControllerWillAddErrorMessageToFlashMessenger()
  {
      $flashMessengerMock = $this->getMockBuilder('\Zend\Mvc\Controller\Plugin\FlashMessenger', array('addErrorMessage'))->getMock();
      $flashMessengerMock->expects($this->once())
          ->method('addErrorMessage')
          ->will($this->returnValue(array()));


      $serviceManager = $this->getApplicationServiceLocator();
      $serviceManager->setAllowOverride(true);
      $serviceManager->get('ControllerPluginManager')->setService('flashMessenger', $flashMessengerMock);

      $this->dispatch('/error/message');

  }
}?>
Decompensation answered 7/10, 2014 at 15:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.