Page 1 of 1

Testing controllers - can you show me code?

Posted: Fri Nov 30, 2007 1:59 pm
by alex.barylski
I've read and seen many examples which demonstrate how to test single classes with few dependencies, such as a model.

1) Initialize the environment inside setup/teardown such as creating and destroying databases.
2) Run each test - voila!

What about when you want to test something more complicated like a application controller?

For example mine are dependent on the model and view, not to mention the front controller and it's request/response objects and additionally the registry and it's various objects and possibly several other layers.

My models are static functions stuffed inside a class but are invoked statically in the controller. Obviously it would make sense to mock/stub or whatever these functions as I care less about the database than I do about the application logic at this point. So having them return phantom values as opposed to connecting to DB, querying, etc.

So I create a stub object with all the methods in place and simply have them return valid values.

I've read SimpleTest and it's Mock object section. I like the idea but I don't think that would work in my controllers because the controllers hardcode some objects such as the model and view, so would it make sense to create a stub class of each model/view and include that instead of the actual mode/view classes?

Incase your wondering why I said SimpleTest Mock's won't work (at least to the best of my knowledge) is because my controller looks something like:

Code: Select all

function myAction()
{
  $smarty-assign('listings' MyModel::queryListings($keywords));
}
Testing this controller would look something like (I assume):

Code: Select all

//
// Setup environment in test case setup/teardown
$request = new Request();
$request->setValue('name', 'Jow Schmoe');

$front = new Front();
$front->setRequest($request);

//
// Execute action on controller
$obj = new myController();
$ret = $obj->myAction();

//
// Run tests
ASSERT($ret == false); // Make sure controller doesn't return FALSE
ASSERT($response->getHeader('Location') == ''); // Make sure controller does a redirect
Given the above snippet of psuedo-code I don't see how I would effectively Mock an object using SImpleTest and it's mocking functionality because it acts as a class factory and actually generates the class interface BUT with a prefix of Mock. Obviously when a Model is invoked inside the myAction it cannot use the Mock created in the setup/teardown of the unit test so a manually created stub class is required instead - unless I have missed something in SimpleTest docs???

I have been trying to determine the difference between Stubs and Mocks since Maugrim posted the question in the forums. If the above problem cannot be solved technically using SimpleTest Mocks - than I would need to create the stub manually, correct??

In which case are stubs and mocks not two different techniques, each required under different situations???

Posted: Fri Nov 30, 2007 6:19 pm
by Jenk
Mocks, mocks and more mocks.

Code: Select all

function myAction() 
{ 
  $smarty-assign('listings' MyModel::queryListings($keywords)); 
}
A perfect example of why globals cause problems :)

Code: Select all

$request = new MockRequest();
$request->expect('foo');

$front = new Front();
$front->setRequest($request);
/* this calls foo on the $request object
   and the mock is verified by the test case */
$front->doSomething();

Posted: Fri Nov 30, 2007 7:24 pm
by alex.barylski
Jenk wrote:Mocks, mocks and more mocks.

Code: Select all

function myAction() 
{ 
  $smarty-assign('listings' MyModel::queryListings($keywords)); 
}
A perfect example of why globals cause problems :)

Code: Select all

$request = new MockRequest();
$request->expect('foo');

$front = new Front();
$front->setRequest($request);
/* this calls foo on the $request object
   and the mock is verified by the test case */
$front->doSomething();
There is reason behind my using a static. Mostly for performance though, because I use a table gateway there is little reason to instantiate an object and there are typically very few calls to model methods inside each controller so it's not messy IMHO. Easy enough to search and replace if nessecary I've done it about 1000 times already.

So unless I somehow configured the front to setup a model object and inject it into the controller space via registry, etc (which doesn't make sense to me) I see no other way, except to use hardcoded class names inside the controller anyways, so I'm not sure where you were going with that. :|

Anyways, I also fail to see how your example addresses my concerns.

I'm not trying to mock the request object, but rather (I believe) trying to stub the Model class inside the controller. There is little point in setting up the environment for a database engine, etc when all I want to test is the application logic. Unfortunately the application logic depends concretely on a statically invoked method, but again, even if I instantiated an object the class would still be hardcoded inside the controller - instanting the model anywhere else makes no sense. No me anyways.

Posted: Fri Nov 30, 2007 7:34 pm
by Jenk
You're not testing the Request object, but you are dependent upon it.. so mock it's behaviour.

Same goes for all dependencies.. DB accessor, etc. Everything that the object you are testing comes into contact with for the duration of the test must be mocked.

Posted: Fri Nov 30, 2007 7:43 pm
by John Cartwright
Hockey wrote:So unless I somehow configured the front to setup a model object and inject it into the controller space via registry, etc (which doesn't make sense to me) I see no other way, except to use hardcoded class names inside the controller anyways, so I'm not sure where you were going with that. :|
One of the benefits of a service locator is to remove named depencency of object "Controller_Foobar" requing "Model_Foobar", and instead changes the dependency to "Controller_Foobar" requires a $model. $model being your Model_Foobar object, Model_Someother object, or a mock.... which is impossible to do with statics.

Posted: Fri Nov 30, 2007 10:19 pm
by alex.barylski
Jcart wrote:
Hockey wrote:So unless I somehow configured the front to setup a model object and inject it into the controller space via registry, etc (which doesn't make sense to me) I see no other way, except to use hardcoded class names inside the controller anyways, so I'm not sure where you were going with that. :|
One of the benefits of a service locator is to remove named depencency of object "Controller_Foobar" requing "Model_Foobar", and instead changes the dependency to "Controller_Foobar" requires a $model. $model being your Model_Foobar object, Model_Someother object, or a mock.... which is impossible to do with statics.
I'm curious, can you show me code on using a simple service locator will remove the dependency of the conrete class *AND* make a controller easier to test? The former part I get, thats obvious. The latter part, your either not understanding my problem or I'm failing to realize something.

Posted: Fri Nov 30, 2007 10:55 pm
by John Cartwright

Code: Select all

class Controller_Action
{
    public function __construct($registry)
    {
          //instead of
          //MyModel::queryListings($keywords)
          $this->model = $registry->get('model');  
          var_dump($this->model->queryListings($keyword);
    }
}
I think maybe looking at the code again will help you understand why it is easier to test. The simple answer is because you can now mock things you arn't testing. Now, we can simply mock $registry and set it to act however we want to. Have fun doing that with static calls :D

Posted: Sat Dec 01, 2007 3:42 pm
by alex.barylski
Jcart wrote:

Code: Select all

class Controller_Action
{
    public function __construct($registry)
    {
          //instead of
          //MyModel::queryListings($keywords)
          $this->model = $registry->get('model');  
          var_dump($this->model->queryListings($keyword);
    }
}
I think maybe looking at the code again will help you understand why it is easier to test. The simple answer is because you can now mock things you arn't testing. Now, we can simply mock $registry and set it to act however we want to. Have fun doing that with static calls :D
Ok. I see where your going with that now. A service locator would eliminate the concrete class dependency and allo easier testing. Although creating a stub class for the model would be possible, using a mock object would be easier as I wouldn't have to actually create a static stub class - at least this is my understanding of Mock supported frameworks.

I'm currently (whether I've misunderstood the document I've read?) under the impresison thought that Mocks are intended to test behavior whereas I think a simple stub class would suffice.

Anyways, which direction I go in depends on a few factors, but at least I now have a solution for mocks - thanks. :)

Posted: Sun Dec 02, 2007 10:42 am
by Weirdan
Usually Mocks can be used as stubs, but not vice versa. Using a PHPUnit notation:

Code: Select all

$mock = $this->getMock('ModelClass');
$mock
   ->expects($this->any())
   ->method('getSomething')
   ->will($this->returnValue('1'))
;
Here you have a stub in fact, because mock wasn't told how many times the method should be called, what arguments it expects etc.