Testing creation/delegation in a FrontController...

Discussion of testing theory and practice, including methodologies (such as TDD, BDD, DDD, Agile, XP) and software - anything to do with testing goes here. (Formerly "The Testing Side of Development")

Moderator: General Moderators

Post Reply
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Testing creation/delegation in a FrontController...

Post by nielsene »

I'm working on my FrontController. I've testing the mapping/dispatch aspect. Now I'm trying to test the delegation aspect. I need to test if it invokes the "invoke" method on the selected class. I can't seem to find a good way to get the mock into the Front Controller.

The FrontController doesn't take any arguments at present so its trivial to kick-off. Adding a factory constructor for the various module controllers that it dispatches to seems wrong/over-complicated just for testing. I guess I could "extract Method" the ModuleController creation and then partialMock it out? I've already used a partial mock before, but it made more sense in that instance I think.


Aren't most FrontControllers fundamentally

Code: Select all

class FrontController {
  function FrontController() {
    // initialize some mapping either from fle or hard-coded
  }
  function invoke() {
    // preform common startup tasks
    // search mapping
    // invoke selected Action/SubController
  }
}
How do you get at any of it to test. I've tested the search mapping piece, but in reality that should be a private function and thus I shouldn't be touching it with tests. Invoke (and the constructor) is the only public interface. Yet, I'ld need to test creation of a database connection at least, as well as logging in the common startup tasks. Along with the current creation/invoke problem.
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

I coded around the problem in the Front Controller, but the same problem is re-appearing in the Module Controllers and it harder to work around here.

Here's how I handled the FrontController problem (actually changed it to FrontDispatcher) the rest of the FrontController will probably be a short procedural script for legacy reasons (the fall back to non-new code needs to includde the target file at the root level of the namespace. not within a function nor class, etc))

Code: Select all

require_once(COMPINABOX.'include/classes/controllers/FrontDispatcher.inc');
require_once(COMPINABOX.'include/classes/controllers/SlidingDoorsAdminController.inc');
Mock::generate("SlidingDoorsAdminController");
Mock::generatePartial("FrontDispatcher","FDTestVersion",
		      array("_createModuleController"));

class TestFrontDispatcher extends UnitTestCase {
  var $savedState;
  
  function TestFrontDispatcher() {
    $this->UnitTestCase('TestFrontDispatcher');
  }

  function setUp() {
    $this->savedState=array("Get"=>$_GET,
			    "Post"=>$_POST,
			    "Cookie"=>$_COOKIE,
			    "Server"=>$_SERVER);
    $_GET=array();
    $_POST=array();
    $_SERVER=array();
    $_COOKIE=array();
  }
  
  function tearDown() {
    $_GET=$this->savedState["Get"];
    $_POST=$this->savedState["Post"];
    $_COOKIE=$this->savedState["Cookie"];
    $_SERVER=$this->savedState["Server"];
  }

  function testInvokeModuleController() {
    $mc =& new MockSlidingDoorsAdminController($this);
    $mc->expectOnce("invoke");

    $_SERVER["REQUEST_URI"]="/register/foo/Admin/bar";
    $dispatcher =& new FDTestVersion($this);
    $dispatcher->FrontDispatcher();
    $dispatcher->setReturnReference("_createModuleController",$mc,
				    array("SlidingDoorsAdminController"));
    $this->assertEqual(1, $dispatcher->dispatch());
    $mc->tally();
  }

  function testFallThrough() {
    $mc =& new MockSlidingDoorsAdminController($this);
    $mc->expectNever("invoke");

    $_SERVER["REQUEST_URI"]="/bar";
    $dispatcher =& new FDTestVersion($this);
    $dispatcher->FrontDispatcher();
    $dispatcher->setReturnReference("_createModuleController",$mc,
				    array("SlidingDoorsAdminController"));
    $this->assertEqual(0, $dispatcher->dispatch());
    $mc->tally();
  }

}

?>

(Note the use of the partialMock...)

Here's the implemntation code:

Code: Select all

class FrontDispatcher {

  var $_mappings=array();
  function FrontDispatcher() {
    $this->_mappings["|register/[-A-Za-z0-9_]*/Admin|"]="SlidingDoorsAdminController";
  }
  
  function _findModuleController() {
    foreach($this->_mappings as $pattern=>$controller)
      if (preg_match($pattern,$_SERVER["REQUEST_URI"]))
	return $controller;
    return "";
  }

  function _createModuleController($className) {
    $mc =& new $className();
    return $mc;
  }

  function dispatch() {
    $moduleControllerName = $this->_findModuleController();
    if ($moduleControllerName!="") {
      $mc=& $this->_createModuleController($moduleControllerName);
      $mc->invoke();
      return 1;
    }
    else return 0;
  }

}
I was able to get away from testing the private method at least. There's still a "bug" in the code, but I don't know how to use TDD to fix it, so I'll probably cheat. _createMockController will need to require_once the controller before instantiating it. Of course it works in the test, because I had to include the file there to mock it.... But in production it hasn't been included yet.....


But overlooking that, I can "assume" that given an appropriate request the SlidingDoorsAdminController gets invoked, so I'm working on that -- after that's one I should have a complete path from Front Controller to actual Handler invocation.

Now I want to hard-code the mappings from Request_URI to pre-sequenced handler chains into the Controller's constructor. (As I feel any sort of meta-data method is overkill for PHP's applicationless memory model) This will involved lots of object creation.... all of which would have to be partially mocked.... yuck.... any other options?
McGruff
DevNet Master
Posts: 2893
Joined: Thu Jan 30, 2003 8:26 pm
Location: Glasgow, Scotland

Post by McGruff »

I think that all looks OK. (One minor point: it's better to set return values/refs on a partial mock before calling the "constructor". The constructor might call one of the partially mocked methods. It doesn't matter here - just a good habit to get into).

Partially mocking the FrontController is an important test simply to check that the mapping works, and that dispatch() will call a module invoke() method correctly. If you want to lose the partial mock, you could create temporary module classes for the test (write/delete the files in setUp/tearDown). These classes would just be shells. In order to test they'd have to return a value from invoke, which means editing the FrontController slightly:

Code: Select all

function dispatch() {
      ....
      ....
      return $mc->invoke();
      ....
    }
$mc->invoke() could just return the class name. In turn you can test to see if dispatch() returns a class name or false/0. Now _createModuleController is getting a proper workout and awareness of a private method has been removed.
Now I want to hard-code the mappings from Request_URI to pre-sequenced handler chains into the Controller's constructor. (As I feel any sort of meta-data method is overkill for PHP's applicationless memory model) This will involved lots of object creation.... all of which would have to be partially mocked.... yuck.... any other options?
Same again: partially mocking is a worthwhile test and/or you can create some temporary, real classes with which to exercise the controller.
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

Hmm I think I will have to return something from invoke anways so your suggestion makes a lot of sense there.

Can you elaborate a little on the shell class files being created in the setup/teardown? I think I understand what you're saying, but it starting to sound like something "too complicated" to but into a test case without tests of its own.
McGruff
DevNet Master
Posts: 2893
Joined: Thu Jan 30, 2003 8:26 pm
Location: Glasgow, Scotland

Post by McGruff »

There's an example here. I'm not sure if the code is as clear as it could be, but the test case _handlerTemplate() method is used to write handler class files which will either return true or null from a try() method.
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

Ahhh that's what that code was doing.... light bulb turns on....
Post Reply