Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using PHPUnit to mock a programatically determined method not specified in the class under test

Using PHPUnit 3.6 I'm trying to test the exec() method in the below controller class. This method does two things:

  1. Determines the name of the method to call based on the object's existing properties, and ...
  2. If the determined controller method is callable it is executed and if not the method throws an exception

The (simplified) source code looks like this:

abstract class CLIController extends Controller
{
  /* irrelevant class details here */

  public function exec()
  {
    $action = ! empty($this->opts->args[0])
      ? $this->opts->args[0]
      : $this->default_action;

    if ( ! $action || ! is_callable(array($this, $action))) {
      $msg = 'Invalid controller action specified';
      throw new LogicException($msg);
    } else {
      $this->$action(); // <---- trying to get code coverage on this line!
    }
  }
}

So my problem is ...

I can't figure out how to get coverage on this part of the code:

} else {
  $this->$action();
}

because I'm not sure how to (or that it's even possible to) test the invocation of a method whose name is not known in the context of the abstract class. Again: the method to be called is declared in child classes. Normally I would just mock an abstract method but I can't in this case because the method doesn't exist yet -- it will be specified by a child class.

What might be the answer ...

  • ??? It may be possible that this line doesn't even need to be covered because it essentially relies on PHP's ability to correctly invoke a callable class method. If I successfully test that exec() throws an exception when it's supposed to, I know that correct functioning of the line in question depends on PHP functioning correctly. Does this invalidate the need to test it in the first place ???
  • If there is some way to mock the abstract class and create a method with a known name to add to the mocked class this would solve my problem and is what I've been trying unsuccessfully to do so far.
  • I know I could create a child class with a known method name but I don't believe it's a good idea to create a concrete child class just to test an abstract parent.
  • It could be that I need to refactor. One thing I don't want to do is leave child classes to implement the exec() function on their own.

What I've tried ...

  • Use some of PHP's reflection capabilities to no avail -- this may perhaps be due to my own inexperience with reflection and not its inability to handle this case, though.
  • Going back and forth through the PHPUnit manual and API docs. Unfortunately, as awesome as PHPUnit is, I often find the API documentation a bit light.

I would really appreciate any guidance on how best to proceed here. Thanks in advance.

like image 330
rdlowrey Avatar asked Jan 04 '12 04:01

rdlowrey


1 Answers

I disagree with your stipulation that "it's [not] a good idea to create a concrete child class just to test an abstract parent." I do this quite often when testing abstract classes and usually name the concrete subclass after the test to make it clear.

class CLIControllerTest extends PHPUnit_Framework_TestCase
{
    public function testCallsActionMethod()
    {
        $controller = new CLIControllerTest_WithActionMethod(...);
        // set $controller->opts->args[0] to 'action'
        $controller->exec();
        self::assertTrue($controller->called, 'Action method was called');
    }
}

class CLIControllerTest_WithActionMethod extends CLIController
{
    public $called = false;

    public function action() {
        $this->called = true;
    }
}

The code to make this test happen is trivial and can be easily verified by inspection.

I'm curious, why use is_callable instead of method_exists to avoid creating the array? It's probably just personal preference, but I'm wondering if there are any semantic differences.

like image 190
David Harkness Avatar answered Sep 21 '22 21:09

David Harkness