I have a class with parent call method in it. I want to make sure that parent have been called so that when I edit tested method and remove that line it will fail tests
My class:
public function myMethod($object)
{
parent::myMethod($object);
..
}
My test (spec):
/*
* #param \Example\Entity\MyEntity $myEntity
*/
function it_cat_call_my_method_example($myEntity)
{
$this->myMethod($myEntity)->shouldParentHaveBeenCalled(); // what to do here?
}
Is that possible?
This is not possible in the way you describe.
What would the benefit of testing a call to parent be? You presumably want to test whether some behaviour the parent implements happens, so call that directly.
Put another way, if I rewrite my class to not call parent and instead implement the same functionality, I don't think the test should fail.
I don't believe it's possible. PHPSpec lets you test your public API (and calls to collaborators) but not anything to do with inheritance.
Related
I have an abstract class I am inheriting from:
abstract class Test
{
public function GetTests()
{
}
}
and I have a concrete that I have been using the abstract classes implementation for most of the time:
class Concrete extends Test
{
// No problemmos
}
I recently had to implement a different version of the GetTests method, and in fact I wanted to overwrite it as it's built into all of my routing:
class Concrete extends Test
{
public function GetTests( $newArgument )
{
// notice $newArgument
}
}
However I get this error message:
Declaration of Concrete::GetTests() should be compatible with Test::GetTests()
Apart from copying the entirety of the functions from the abstract class for this concrete, even though I only need to implement this one method differently... Is there a way of getting around this?
I do understand that I could have:
abstract class Test
{
abstract public function GetTests();
}
But this is why I am snookered, because I no longer have the ability to modify how the underlying Test class is implemented... doh!... Unless I really have to..
Thanks to all great answers...
I have decided to de snooker myself (it's going to hurt but it's going to be worth it) and I will instantiate the Test class inside the Concrete class, implement concrete versions of all the Test class methods, and then inside them just call the instantiated Test class... This means in the future (or indeed now) I can simply not call that feature...
For context:
/* no longer abstract */ class UnitOfWorkController
{
public function GetUnits()
{
// Implementation
return View::make(...);
}
}
and...
class SomethingController /* no longer extends the UnitOfWorkController */
{
private $unitOfWorkController;
public function __Construct()
{
$this->unitOfWorkController = new UnitOfWorkController();
}
public function GetUnits()
{
return $this->unitOfWorkController->GetUnits();
// or I could just implement my own junk
}
}
Your concrete subclass is in violation of the Liskov Substitution Principle, which to cut a long story short says that if an object of class X can be processed by a given piece of code, then every possible subclass of X must also be able to be processed by the same piece of code.
Say I wanted to make another subclass of Test and wanted to implement my own GetTests method. The base class method doesn't accept any arguments at all, so that suggests that, if my subclass is to be substitutable for its superclass, my implementation of that method cannot take any arguments either. If I give my implementation arguments, then it no longer conforms to the specification as laid down by the superclass.
If I have code that does:
$object = new Test;
$test -> GetTests ();
then I can't substitute my subclass of Test without also changing the calling code to pass in an argument. Likewise if I do change it, then I have another subclass of Test that doesn't require an argument for GetTests then the code would have to change again. In fact the same code simply can't be used as is with both subclasses without having to jump through some hoops to determine the actual class and using the appropriate calling convention which means needing to know things about the class I'm about to use that I shouldn't need to know.
PHP is less strict than most OO languages about subclass method signitures matching their superclass, but it will issue a warning if they don't match. The only way to fix the warning is to have all subclasses have the same method signatures as the superclass they inherit from.
Child methods must have the same signature as the same method in a parent class. This includes required parameters and their typecasting.
For example, a child class of the following method must also have one parameter, and the parameter must cast to the ArgumentType class or a child of thereof.
public function something(ArgumentType $Argument)
{
}
You can, however, make the parameter optional by setting it to null or any other value:
public function something(ArgumentType $Argument = null)
{
}
In this case, child methods may omit this parameter.
From the PHP docs, see http://php.net/manual/en/language.oop5.abstract.php:
[…] Furthermore the signatures of the methods must match, i.e. the type hints and the number of required arguments must be the same. For example, if the child class defines an optional argument, where the abstract method's signature does not, there is no conflict in the signature.
The method signature of Concrete::GetTests() has a variable while Test::GetTests() does not. Since you have already defined this method within Test, it is now being inherited. The inherited version is not compatible with your overridden version.
Here are your options:
Add $newArgument to the parameters list in Test::GetTests().
Remove $newArgument from the parameters list in Concrete::GetTests().
Rename Concrete::GetTests() to something else.
PHP does not support this, as the error message says. If you want to override the function, it has to have the same footprint, which in your case it doesn't
What you could do is use a magic method: http://php.net/manual/en/language.oop5.overloading.php#object.call
the parameter array is a separate entity, so you 'decide' in your code (which you can override) what to do with which parameter.
I wanted to link a blog I read about this, but couldn't find the one I was thinking of. There is this rather strangely formatted one, not sure if it any good, but it does touch on some of the issues.
You could obviously add the argument to the parent, but this is 'leaking' upwards. If other childeren want even more, you'd get a big party of random paramteres that all can be nulled.
Within a single class, I am able to use the _call/_callStatic functions to call a function before any function is actually run. However, I'm yet to find a solution that I can apply to the parent, and it will be inherited by the children.
The only method I can think of is putting a small snippet of code within each class that calls a function in the parent, which contains my code.
Are there any better solutions that I can implement?
As long as you are at least on php 5.4, you could use traits.
http://php.net/traits
This will need some adjustments in your code but could allow the desired behavior.
Do you have some more information on your use case? I think of logging from your description, is this correct?
Unfortunately there are not magic methods in PHP which get run whenever any method is called.
I think the solution you are looking for is a decorator, see this article for the tips on decorating methods / classes in PHP.
how to implement a decorator in PHP?
It's hard to help you without seeing some code. However, assuming that you don't re-define __call/__callStatic in the subclass, it should simply inherit the method from it's superclass.
If you do re-define __call/__callStatic in the subclass, it overrides the superclass' definition. So you'd somehow have to call the superclass' method. In order to do this you can use the parent keyword. See this example:
class SuperClass
{
public function __call($name, $arguments)
{
// Do some stuff
}
}
class SubClass extends SuperClass
{
public function __call($name, $arguments)
{
// Execute the parent's __call() method
return parent::__call($name, $arguments);
// Do some extra stuff here
}
}
Using PHPUnit 3.6 I'm trying to test the exec() method in the below controller class. This method does two things:
Determines the name of the method to call based on the object's existing properties, and ...
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.
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.
Here is a simple view helper (notice the pass-by-reference argument):
class Zend_View_Helper_MyViewHelper extends Zend_View_Helper_Abstract
{
public function MyViewHelper(&$array)
{
unset($array['someExistingKey']);
}
}
This does not work in the view. $array['someExistingKey'] is still set (except within the immediate context of the method). Zend must be doing something to prevent the array from being passed in by reference. Any ideas on a solution?
When you call $this->MyViewHelper($array) from your templates you are not actually calling the helper class directly, Zend_View is instantiating the class and calling it for you. So I think you might have trouble getting this working. Your best bet is probably to use Zend_Registry, or refactor to take a different approach not requiring a global.
I just thought of a workaround. You just have to call the helper manually, instead of letting ZF call it through call_user_func_array.
Ref.php
class Zend_View_Helper_Ref extends Zend_View_Helper_Abstract
{
public function removeFromRef(&$ref)
{
// change your var value here
unset($ref['key']);
}
/**
* ZF calls this for us, but we'll call what we want, so you can skip this.
*/
// public function ref()
// {}
}
As you can see, you can skip the convention of having to name your main method as the filename, but I still recommend it.
Now, you can pass references in views/controllers:
// in view:
$this->getHelper('Ref')->removeFromRef($someVar2Change);
// in controller
$this->view->getHelper('Ref')->removeFromRef($someVar2Change);
Basically, this is what $this->ref() does: gets the helper, then calls call_user_func_array.
Some people may have problems using $this->getHelper('Ref')->ref() instead of $this->ref() though, but it works.
This is a tricky one.
I am "emulating" ZF Bootstrapping (surface appearance). Don't ask me why, call it academic interest. So I have a Bootstrap Abstract with a method "run" that iterates over itself to locate any methods prefixed with "init".
The application looks for a user defined class which extends this class, in which the user can define any number of methods in this way. However, I want to prevent the user from being able to execute the "run" command of it's parent class, while still exposing the same command for the client code.
class Bootstrap_Abstract{
protected final function run(){
// if method exists that starts 'init' - execute the method
}
}
class Bootstrap extends Bootstrap_Abstract(){
public function initSomething(){
//do something
}
//PREVENT THIS
public function initRun(){
$this->run();
}
}
//application code, not exposed to user - changes in behaviour require changes in this code directly
class Application(){
$Bootstrap = new Bootstrap();//load user bootstrap
$Bootstrap->run();
}
To determine "what" called a particular method, look into debug_backtrace
Post Script:
the problem with my original code was an error in design. The responsibility for iterating through the Bootstrap methods should have been given to the invoking class, not the target class itself.
I solved this problem by moving the function out to the invoker. Funny how obvious/simple refactoring is in hindsight...