We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

How to unit testing controller action?

Hi

What is the correct way to do unit test a controller action?

class AController extends Controller 
{
     public function getAction()
     {
           $id = $this->request-getQuery('id');

           ... //codes block B (e.g. $id = $id * 2); 

           $a = A::findFirst($id);

              ... //codes block C 

           echo $a ? "Found" : "Not Found";
     }     
}

In this test I do not want to touch DB to do the query, so I mock the 'A' model. But I am not sure how to assign the mock model to the controller. something like this:

class AControllerTest extends UnitTestCase
{
    protect function setUp()
    {
           parent::setUp();
            // please assume I have setup relative services in below.
           $this->di->set('request', ..);
           $this->di->set('modelsManager', ..);
           $this->di->set('dispatcher', ...)
    }

    public function testGetAction()
    {
         $a_mock = $this->getMockBuilder('A')
                      ->setMethods(array('findFirst'))
                      ->getMock();

         $a_mock->expects($this->once())
                       ->method('findFirst')
                      ->will($this->returnValue(null));

          // I am lost in here, 

         $controller = new AController();
         $controller->getAction();

    }
}
  1. I do not what to do with $a_mock, how to assign it to controller. so the line "$a = A::findFirst($id);" will return null.
  2. Even this will works, still it only test code block C. how do I test code bock B ?

Any help would be greatly appreciated, thanks in advance.



39.3k
Accepted
answer

I haven't tested this but what you could do is implement a setAModel() in your controller and put your mock model there.

class AController extends Controller 
{
  private $a = null; 

  public function setAModel($model)
  {
    $this->a = $model;
  }

  public function getAction()
  {
    $id = $this->request-getQuery('id');

    ... //codes block B (e.g. $id = $id * 2); 

    if (isnull($this->a)) {
      $this->a = new \A();
    }

    $mymodel = $this->a;

    $a = $mymodel::findFirst($id);

    ... //codes block C 

    echo $a ? "Found" : "Not Found";
  }     
}

Test

class AControllerTest extends UnitTestCase
{
    protect function setUp()
    {
       parent::setUp();
        // please assume I have setup relative services in below.
       $this->di->set('request', ..);
       $this->di->set('modelsManager', ..);
       $this->di->set('dispatcher', ...)
    }

    public function testGetAction()
    {
        $a_mock = $this->getMockBuilder('A')
                       ->setMethods(array('findFirst'))
                       ->getMock();

        $a_mock->expects($this->once())
               ->method('findFirst')
               ->will($this->returnValue(null));

        // I am lost in here, 

        $controller = new AController();
        $controller->setAModel($a_mock);
        // do other stuff
        $controller->getAction();
    }
}


7.0k

Thank you so much for your reply and solution. Yes, it will works.

class AController extends Controller 
{
  private $_model = null; 

  public function setModel($model)
  {
        if (!$this->_model) {
              $this->_model = $model;
        }
  }

   public function getModel()
   {
          if (!$this->_model) {
               $this->_model = new A();   
          }
          return $this->_model;
   }

  public function getAction()
  {
    $id = $this->request-getQuery('id');

    ... //codes block B (e.g. $id = $id * 2); 

    $mymodel = $this->model();

    $a = $mymodel::findFirst($id);

    ... //codes block C 

    echo $a ? "Found" : "Not Found";
  }     
}