To state the problem simply:
Say I have two abstract classes, Foo and Baz.
And say Baz defines a method, biz(), which expects an instance of Foo.
public function biz(Foo $foo): void {...}
Is it possible to test that Baz->biz() calls a specific method of the Foo instance passed to it?
My hunch after much struggle is that it may not be possible to write such a test...
Below is an example of what I am trying to accomplish:
abstract class Foo ()
{
abstract public function bar(): void;
...
}
abstract class Baz()
{
public function biz(Foo $foo): void
{
$foo->bar();
}
abstract public function buz(): void;
...
}
final class BazTest extends TestCase
{
public function testBazzerCallsFooInstancesBarMethod() {
$mockFoo = $this->getMockBuilder(Foo::class)
->setMethods(['bar'])
->getMock();
$mockBaz = $this->getMockBuilder(Baz::class)
->setMethods(['biz', 'buz'])
->getMock();
/**
* Problem: How do I test that $mockBaz->biz() calls $mockFoo->bar() internally,
* or is this not possible? The following approach does not work...
*/
$mockFoo->expects($this->once())
->method('bar');
$mockBaz->biz($mockFoo);
}
}
Related
I've setup an abstract class that must execute methods from another instanciated class.
So I setup an anonymous class that extends my abstract class and in the implemented method, I want to access the methods of that "other instanciated class", but for my IDE to help me to find the correct name of the methods and arguments, I must write somewhere the type.
abstract class blah
{
protected object $reference;
public function __construct(object $reference)
{
$this->reference = $reference;
$this->hello();
}
abstract public function hello();
}
class world
{
public function __construct()
{
new class($this) extends blah {
public function hello()
{
$this->reference->display();
}
};
}
public function display()
{
echo 'hello world';
}
}
new world();
I can't change the type from "object" to something else to allow my IDE to show the methods
public function __construct()
{
new class($this) extends blah {
protected world $reference;
public function hello()
{
$this->reference->display();
}
};
}
this throws
Fatal error: Type of blah#anonymous::$reference must be object (as in class blah)
I could copy the attribute "reference" to another variable and set a typehint to "world", but that's not the best
public function __construct()
{
new class($this) extends blah {
protected world $reference;
public function hello()
{
/** #var world $hello */
$hello = $this->reference;
$hello->display();
}
};
}
what better solution do I have ?
edit: this seems to work, is it the best way ?
new class($this) extends blah {
/** #var world $reference */
protected object $reference;
public function hello()
{
$this->reference->display();
}
};
It's worth understanding why PHP is telling you that you can't change the property type.
Since this is a protected property, it can be read and written by either the parent or child class. For instance, the parent class might have methods like this, which would be inherited by the child class:
public function getReference(): object {
return $this->reference;
}
public function setReference(object $newReference): void {
$this->reference = $newReference;
}
public function setReferenceToDefault(): void {
$this->reference = new SomethingBoring;
}
If you specify world as the type of $reference in a child class, the getReference method will work fine - any instance of world is an object, so the return type is OK. But the setReference() and setReferenceToDefault() methods would fail, because they try to assign something that isn't of type world.
Essentially, by declaring a protected property on the base class, you are setting a contract for all child classes, and PHP is enforcing it for you. The technical term for this is "contravariance": a child class can be more generous in what it accepts, but it can't reject values the parent would accept. The opposite is "covariance", which applies to returning values: the child class can be more specific about what it will return, but can't return something the parent class never would. When something is both input and output, you get both restrictions, so can't vary the type at all.
Since you don't actually use the property on the base class, this restriction isn't actually needed, so in this case one option is simply not to define the property or constructor on the base class. In other cases, you might want to use traits, which are "compiler assisted copy-and-paste": they don't assert any relationship between two classes, but they save you writing the same code more than once.
If you want it only for IDE, then use documentation block to overwrite type declaration (if IDE supports it):
/**
* #property world $reference
*/
new class($this) extends blah {
public function hello()
{
$this->reference->display();
}
};
E.g. PHPStorm, while not supporting that for anonymous class, supports for normal class description:
My class Foo has a method called Bar which is when called logs a debug message. Class Foo gets \Psr\Log\LoggerInterface $logger in its __contruct method. I'v created a testBar method in my FooTest Class but debug method in my Bar method is giving following error
PHP Fatal error: Class Mock_LoggerInterface_a49cf619 contains 8 abstract >methods and must therefore be declared abstract or implement the remaining >methods (Psr\Log\LoggerInterface::emergency, Psr\Log>\LoggerInterface::alert, Psr\Log\LoggerInterface::critical, ...) in /var/www/html/myproject/vendor/phpunit/phpunit-mock-objects>/src/Generator.php(264) : eval()'d code on line 1
My class code is given below
use Psr\Log\LoggerInterface;
class Foo {
private $logger;
private $myclassObject;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function Bar ()
{
// some code
$logger->debug ('debug message')
}
}
My test class given below
use PHPUnit\Framework\TestCase;
class FooTest extends TestCase
{
private $logger;
public function setUp()
{
$this->logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')
->setMethods(null)
->getMock();
$this->logger->expects($this->any())
->method('debug')
->willReturn('Message Logged');
}
$this->myclassObject = $this->getMockBuilder('MyVendor\MyModule\Model\Foo')
->setMethods(['__construct'])
->setConstructorArgs(['$logger'])
->disableOriginalConstructor()
->getMock();
public function testBar()
{
$this->assertEquals($expected_result,$this->myclassObject->Bar());
}
}
I'm expecting to see a Successful unit test with stubbed debug method logging 'Message Logged'
I'm ignoring the syntax issue with defining $this->myclassObject at the class level, because I assume that's a typo from you creating this question.
I think that you have two problems:
You're overriding PHPUnit's ability to mock a class/interface's abstract/interface methods by specifying null in LoggerInterface's setMethods, which tells it not to mock anything
You're both disabling the Foo constructor and providing constructor args (in quotes as a scalar value of the variable name)
You also are referencing $logger which doesn't exist.
I'd also suggest that in your example, you don't need to partially mock Foo at all, since you're not mocking any of its functionality at this point. You can simply call new Foo($this->logger). I assume, however, that your example is cut down and you do need to partially mock other parts of the class, so will leave it for now.
Try this:
class FooTest extends TestCase
{
private $logger;
private $myclassObject;
protected function setUp()
{
$this->logger = $this->createMock('\Psr\Log\LoggerInterface');
$this->logger->expects($this->any())
->method('debug')
->willReturn('Message Logged');
$this->myclassObject = $this->getMockBuilder('\MyVendor\MyModule\Model\Foo')
->setConstructorArgs([$this->logger])
->getMock();
}
public function testBar()
{
$this->assertEquals($expected_result, $this->myclassObject->Bar());
}
}
I am trying to figure out how to import a large number of PHP class functions on the fly. For example...
class Entity
{
public function __construct($type)
{
require_once $type."_functions.php"
}
// ...
}
$person = new Entity("human");
$person->sayhi();
$cow = new Entity("cow");
$cow->sayhi();
human_functions.php:
class Entity redefines Entity
{
public function sayhi()
{
echo "Hello world!";
}
}
cow_functions.php:
class Entity redefines Entity
{
public function sayhi()
{
echo "Moo!";
}
}
I have found a few possibilities like classkit_method_redefine() and runkit_method_redefine() (which are "experimental", and they cannot modify the currently running class anyway). I am on PHP 5.3.3 right now, so I can't use Traits (Not sure if that is what I am looking for anyways). I have had success redefining the handler variable like this:
// Example 2:
class OtherEntity { /* Code Here */ }
class Entity
{
public function __construct($type)
{
global $foo;
unset($foo);
$foo = new OtherEntity();
}
}
$foo = new Entity();
But, this feels like a very hacky method. More importantly, if I don't name every instance of the class $foo, then it will not work. Are there any workarounds for what I am trying to do?
Note: I am aware that I can extend a class, but in my case when the Entity class is initiated, there is no safe way to know in advance what subclass it would need to be initiated with. Perhaps there is a method I could write, such as:
public function changeClass
{
this->class = OtherEntity;
}
Thanks for your help!
Here's an idea of a possible solution you could try. Let the Cow and Human classes extend the Entity class. However, the Entity class would use a factory to instantiate the objects based on if the value was safe. Let's look at this in more detail:
/*
* Class Entity should not be able to be instantiated.
* It should contain a factory to instantiate the
* appropriate entity and an abstract function declaring
* the method that each entity will need to implement.
*/
abstract class Entity {
public static function factory($type) {
return (is_subclass_of($type, "Entity")) ? new $type() : FALSE;
}
abstract public function sayHi();
}
/*
* Human class extends Entity and implements the
* abstract method from Entity.
*/
class Human extends Entity {
public function sayHi() {
echo "Hello World!";
}
}
/*
* Cow class extends Entity and implements the
* abstract method from Entity.
*/
class Cow extends Entity {
public function sayHi() {
echo "Moo!";
}
}
Now to use this method, call the factory method and if all works well, it'll instantiate the proper class which will extend Entity.
$person = Entity::factory("Human");
$person->sayHi();
$cow = Entity::factory("Cow");
$cow->sayHi();
Using, is_subclass_of() will keep you safe because if the passed in value is not a class that extends Entity, you'll be returned a value of FALSE.
If you'd like to see the above code in action, copy the above php code and test it out on phpfiddle.org.
One thing you can do is create Human and Cow as subclasses of Entity. When you do new Entity("Human"), you can store a newly created Human object inside the Entity instance.
Then you can use __call to redirect method calls to the "child element".
class Entity{
private $child;
public function __construct($type){
$this->child = new $type;
}
public function __call($func, $params=array()){
$method = method_exists($this, $func)
? [$this, $func] : [$this->child, $func];
return call_user_func_array($method, $params);
}
}
class Human extends Entity{
public function __construct(){}
public function sayhi(){
echo "Hello world!";
}
}
class Cow extends Entity{
public function __construct(){}
public function sayhi(){
echo "Moo!";
}
}
$person = new Entity("Human");
$person->sayhi();
$cow = new Entity("Cow");
$cow->sayhi();
The only downside is that $person and $cow are both Entity objects.
So you can't make an abstract static function in php.
The alternatives as I see them are to:
Make the function non-static and write extra boilerplate code to create and store the object so I can access that function.
abstract class Foo {
abstract public function bar();
}
abstract class Good {
public function bar() {
...
}
}
// boilerplate to access Good->bar()... potentially a lot in multiple files
$g = new Good();
$g->bar();
Fill in the static function in my abstract class with a BadMethodCallException, so that any call to a child class which doesn't implement it will throw the exception.
abstract class Foo {
public static function bar() {
throw new BadMethodCallException("Not Implemented By Child Class :(");
}
}
class Good extends Foo {
public static function bar() {
// ...
}
}
class Bad extends Foo {
// no bar implementation
}
Good::bar(); // works
Bad::bar(): // exception
I'm leaning towards 2. but was wondering if there's any community consensus on this issue or best practices.
I ended up making an interface with a static function, then implementing the interface in the abstract class. This forces the child classes to define the method, which is basically what I wanted with an abstract static function.
interface ModelFactoryInterface {
public static function offer();
}
abstract class ModelHelper implements ModelFactoryInterface {
protected $tester;
public function __construct($tester) {
$this->tester = $tester;
}
}
/* Location
* ------------------------------------------------------ */
final class LocationHelper extends ModelHelper {
public static function offer() {
return new Location(...)
}
}
I'm writing a unit test for a class method that calls another class's method using a mock, only the method that needs to be called is declared as final, so PHPUnit is unable to mock it. Is there a different approach I can take?
example:
class to be mocked
class Class_To_Mock
{
final public function needsToBeCalled($options)
{
...
}
}
my test case
class MyTest extends PHPUnit_Framework_TestCase
{
public function testDoSomething()
{
$mock = $this->getMock('Class_To_Mock', array('needsToBeCalled'));
$mock->expects($this->once())
->method('needsToBeCalled')
->with($this->equalTo(array('option'));
}
}
Edit: If using the solution provided by Mike B and you have a setter/getter for the object you're mocking that does type checking (to ensure the correct object was passed into the setter), you'll need to mock the getter on the class you're testing and have it return the other mock.
example:
class to be mocked
class Class_To_Mock
{
final public function needsToBeCalled($options)
{
...
}
}
mock
class Class_To_MockMock
{
public function needsToBeCalled($options)
{
...
}
}
class to be tested
class Class_To_Be_Tested
{
public function setClassToMock(Class_To_Mock $classToMock)
{
...
}
public function getClassToMock()
{
...
}
public function doSomething()
{
$this->getClassToMock()
->needsToBeCalled(array('option'));
}
}
my test case
class MyTest extends PHPUnit_Framework_TestCase
{
public function testDoSomething()
{
$classToTest = $this->getMock('Class_To_Be_Tested', array('getClassToMock'));
$mock = $this->getMock('Class_To_MockMock', array('needsToBeCalled'));
$classToTest->expects($this->any())
->method('getClassToMock')
->will($this->returnValue($mock));
$mock->expects($this->once())
->method('needsToBeCalled')
->with($this->equalTo(array('option'));
$classToTest->doSomething();
}
}
I don't think PHPUnit supports stubbing/mocking of final methods. You may have to create your own stub for this situation and do some extension trickery:
class myTestClassMock {
public function needsToBeCalled() {
$foo = new Class_To_Mock();
$result = $foo->needsToBeCalled();
return array('option');
}
}
Found this in the PHPUnit Manual under Chapter 11. Test Doubles
Limitations
Please note that final, private and static methods cannot be stubbed or mocked. They are ignored by PHPUnit's test double functionality and retain their original behavior.
I just stumbled upon this issue today. Another alternative is to mock the interface that the class implements, given that it implements an interface and you use the interface as type hinting.
For example, given the problem in question, you can create an interface and use it as follows:
interface Interface_To_Mock
{
function needsToBeCalled($options);
}
class Class_To_Mock implements Interface_To_Mock
{
final public function needsToBeCalled($options)
{
...
}
}
class Class_To_Be_Tested
{
public function setClassToMock(Interface_To_Mock $classToMock)
{
...
}
...
}
class MyTest extends PHPUnit_Framework_TestCase
{
public function testDoSomething()
{
$mock = $this->getMock('Interface_To_Mock', array('needsToBeCalled'));
...
}
}