PHPUnit turning an instance of a class into a mock after instantiation - php

Is there a way to create a mock class with PHPUnit which I can then create a new instance of by using its class name?
I have an interface which defines two methods. Something like:
interface FooInterface {
function getA();
function getB();
}
I then have another class which accepts a class name, creates an instance of that class, checks if it is an instance of what it expects (FooInterface) and then calls two methods on that class to get some information.
class FooInfo {
protected $a;
protected $b;
public function __construct($fooClass) {
$foo = new $fooClass;
if (!($foo instanceof FooInterface)) {
throw new \Exception();
}
$this->a = $foo->getA();
$this->b = $foo->getB();
}
}
I know how to mock an object just fine. The problem is, since this class accepts a class name, not an object (it is a part of a Manager which creates instances of the given class as needed), I can't use a normal mock object.
I tried to make a mock object then use that class name. It seems to create the object just fine, and even seems to have the functions I mocked out. However, it doesn't seem to follow the will($this->returnValue('myValue')) portion I set up later.
public function testConstruct()
{
$foo = $this->getMockForAbstractClass('Foo', array('getA', 'getB'));
$foo->expects($this->any())->method->('getA')->will($this->returnValue('a'));
$foo->expects($this->any())->method->('getB')->will($this->returnValue('b'));
$copyClass = get_class($foo);
$copy = new $copyClass();
// Passes
$this->assertTrue(method_exists($copy, 'getA');
// Fails, $copy->getA() returns null.
$this->assertEquals($copy->getA(), $foo->getA());
}
So, it does have the functions which were mocked, but they all return null.
Any ideas?

To use the new keyword in the constructor of a class is a rather bad habit exactly for the reasons you're experiencing now, even given your flexible use case.
Your test will not work because the mock you create will never be used, since your class will always create a real instance of the injected classname.
That said, what you want to do can be done with padraic's excellent mocking library mockery!
What you need is an 'instance mock':
$mock = \Mockery::mock('overload:MyNamespace\MyClass');
You can define your expectations on that mock which will be transferred to the real object, as soon as it is instantiated.
Integrating mockery with phpUnit is easy and well explained in the readme of the project.
And btw. each unit test should optimally make one assertion only!

The methods getA and getB return null because you have not specified what they should return. You did specify that for the abstract $foo by calling some methods. There's no way around that.
Since it's hard (if not impossible) to test the function, I would rewrite the code itself. Testing would be easy if your constructor would require a class instance rather than a class name. If you also must accept strings, you could write a few lines to check for a string input and create a class if a string is provided.

Related

Mock builder isnt replacing methods signatures

Aren't mocks supposed to replace methods and their parameters?
I don't understand how this works, I copy-paste examples but i run into trouble on every single example that should work out of the box..
The problem i run into is that the methods do not have their parameters removed, so if a method has a certain dependency as a parameter, i HAVE to pass it to the mock's method too..
The manual even says that by default all methods are replaced by empty methods that return null.
I'm using 5.7(php 5.6)
class SomeClass
{
public function doSomething(\Exception $e)
{
// Do something.
}
}
class StubTest extends TestCase
{
public function manualExampleDoesntWork()
{
// Create a stub for the SomeClass class.
$stub = $this->createMock(SomeClass::class);
// Configure the stub.
$stub->method('doSomething')
->willReturn('foo');
// Calling $stub->doSomething() will now return
// 'foo'.
$this->assertEquals('foo', $stub->doSomething());
}
public function ...
{
// Should be enough, doesnt work
$stub = $this->createMock(SomeClass::class);
$stub->doSomething(); // error
// Neither
$stub = $this->createMock(SomeClass::class);
$stub->method('doSomething')
->withAnyParameters()
->willReturn('foo');
$stub->doSomething(); // error
// Same for manually building with the mockbuilder..
}
}
Results in: Argument 1 passed to Mock_***95::doSomething() must be an instance of Exception, none given
This is just one example, I've tried every possible variation of creating mocks, all results in me not being able to replace the parameters..
It is about method overloading / overriding. PHP does not support this stuff same way as, say, C++ or Java do -- meaning you can't declare methods having same name and different arguments lists either in the scope of current class or further in inheritance chain. Methods signatures must stay the same. One exception is __construct which is overridable with different signature. Should be also mentioned that PHP does have overloading concept but it is a bit different idea. You can read more here and here.
Concerning your particular example all said above means you must call doSomething with either instance of Exception class or with a stub for Exception because the doSomething method of of SomeClass 's stub still has same signature as authentic SomeClass. Also another option could be declaring it as public function doSomething(\Exception $e = NULL).
The implementation of the doSomething method in SomeClass mock is really empty by default -- till you configure it.

What's the point of a static method that returns an instance of the class it's a part of?

Sometimes when I look at code other people have written I see something like the following:
<?php
namespace sys\database;
class Statistics {
public function __construct() {
// Database statistics are gathered here using
// private methods of the class and then set to
// class properties
}
public static function getInstance() {
return new \sys\database\Statistics();
}
// ...
}
So the static function getInstance() simply returns an object of the class it belongs to. Then, somewhere else in the code I come across this:
$stats = \sys\database\Statistics::getInstance();
Which simply sets $stats to an instance of the Statistics object, ready for its class properties to be accessed to get various database statistics.
I was wondering why it was done this way as opposed to just using $stats = new \sys\database\Statistics();. At the end of the day, all the logic to gather statistics is in the constructor and the getInstance() method doesn't do anything other than returning a new object.
Is there something I'm missing here?
This is supposed to be an implementation of the Singleton pattern: http://www.oodesign.com/singleton-pattern.html
The pattern is used to never allow more than one instance of the class to be created.
However, there are a couple of flaws with the implementation you provided: the constructor should be private, and there should be a single private static instance of the class, returned every time the getInstance method is called.
This is supposed to be an implementation of the Singleton pattern, which is a term used to describe a class which can only exist once for run-time.
It seems the implementation you have is flawed however because:
there is no check to see if the class exists yet and
code can create multiple instances by calling the constructor directly (it should be made private)
That's a [bad] implementation of the Singleton pattern.
As a rule of thumb, you should avoid such pattern in favour of more convenient Dependency Injection, for instance.

How to test factory classes?

Given this class:
class MyBuilder {
public function build($param1, $param2) {
// build dependencies ...
return new MyClass($dep1, $dep2, $dep3);
}
}
How can I unit test this class?
Unit-testing it means I want to test its behavior, so I want to test it builds my object with the correct dependencies. However, the new instruction is hardcoded and I can't mock it.
For now, I've added the name of the class as a parameter (so I can provide the class name of a mock class), but it's ugly:
class MyBuilder {
public function build($classname, $param1, $param2) {
// build dependencies ...
return new $classname($dep1, $dep2, $dep3);
}
}
Is there a clean solution or design pattern to make my factories testable?
Factories are inherently testable, you are just trying to get too tight of control over the implementation.
You would check that you get an instance of your class via $this->assertInstanceOf(). Then with the resulting object, you would make sure that properties are set properly. For this you could use any public accessor methods or use $this->assertAttribute* methods that are available in PHPUnit.
http://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions.assertEquals
Many of the common assertions also have the ability to check attributes for protected and private properties.
I wouldn't specify the classname in your parameter list, as your usage is that the factory will only return one type and it is only the dependencies that are changed. Making it return a mock object type is unnecessary and makes your test more complicated.
The test would end up looking like this:
public function testBuild() {
$factory = new MyBuilder();
//I would likely put the following into a data provider
$param1 = 'foo';
$param2 = 'bar';
$depen1 = 'boo';
$depen2 = 'baz';
$depen3 = 'boz';
$object = $factory->build($param1, $param2);
$this->assertInstanceOf('MyClass', $object);
//Check the object definition
//This would change depending on your actual implementation of your class
$this->assertAttributeEquals($depen1, 'attr1', $object);
$this->assertAttributeEquals($depen2, 'attr2', $object);
$this->assertAttributeEquals($depen3, 'attr3', $object);
}
You are now making sure that your factory returns a proper object. First by making sure that it is of the proper type. Then by making sure that it was initialized properly.
You are depending upon the existence of MyClass for the test to pass but that is not a bad thing. Your factory is intended to created MyClass objects so if that class is undefined then your test should definitely fail.
Having failing tests while your developing is also not a bad thing.
So what do you want to test?
so I want to test it builds my object with the correct dependencies.
I do see a problem with this. It's either possible that you can create an object with incorrect dependencies (which should not be the case in the first place or tested in other tests, not with the factory) or you want to test a detail of the factory that you should not test at all.
Otherwise - if it's not mocking the factory what you're looking for - I see no reason why a simple
$actual = $subject->build($param1, $param2);
$this->assertInstanceOf('MyClass', $actual);
would not make it. It tests the behavior of the factory build method, that it returns the correct type.
See as well Open-Close-Principle
For tests, you can just create your MockBuilder which extends from your Builder:
class MyMockBuilder extends MyBuilder {
public function build($param1, $param2) {
// build dependencies ...
return new MyMockClass($dep1, $dep2, $dep3);
}
}
Making the classname a parameter 1:1 seems not practical to me, because it turns the factory over into something different. The creating is a detail of the factory, nothing you externalize. So it should be encapsulated. Hence the MockBuilder for tests. You switch the Factory.
As I see it, you ned to verify two things for that builder:
the correct instance is returned
values, that are injected are the right ones.
Checking instance is the easy part. Verifying values needs a bit of trickery.
The simples way to do this would be altering the autoloader. You need to make sure that when MyClass is requested for autoloader to fetch, instead of /src/app/myclass.php file it loads /test/app/myclass.php, which actually contains a "transparent" mock (where you with simple getters can verify the values).
bad idea
Update:
Also, if you do not want to mess with autoloader, you can just at th top of your myBuilderTest.php file include the mock class file, which contains definition for MyClass.
... this actually seems like a cleaner way.
namespace Foo\Bar;
use PHPUnit_Framework_TestCase;
require TEST_ROOT . '/mocks/myclass.php'
class MyBuilderTest extends PHPUnit_Framework_TestCase
{
public function MyBuilder_verify_injected_params_test()
{
$target = new MyBuilder;
$instance = $target->build('a', 'b');
$this->assertEquals('a', $instance->getFirstConstructorParam();
}
}

phpunit testing method that calls other class methods which need mock

I'm trying to create a pretty standard unit test where I call a method and assert it's response, however the method I'm testing calls another method inside the same class which does a little bit of heavy lifting.
I want to mock that one method but still execute the method I'm testing as is, only with the mocked value returned from the call to the other method.
I've dumbed down the example to make it as simple as possible.
class MyClass
{
// I want to test this method, but mock the handleValue method to always return a set value.
public function testMethod($arg)
{
$value = $arg->getValue();
$this->handleValue($value);
}
// This method needs to be mocked to always return a set value.
public function handleValue($value)
{
// Do a bunch of stuff...
$value += 20;
return $value;
}
}
My attempt at writing the tests.
class MyClassTest extends \PHPUnit_Framework_TestCase
{
public function testTheTestMethod()
{
// mock the object that is passed in as an arg
$arg = $this->getMockBuilder('SomeEntity')->getMock();
$arg->expects($this->any())
->method('getValue')
->will($this->returnValue(10));
// test handle document()
$myClass = new MyClass();
$result = $myClass->testMethod($arg);
// assert result is the correct
$this->assertEquals($result, 50);
}
}
I have tried mocking the MyClass object, but when I do that and call the testMethod it always returns null. I need a way to mock the one method but leave the rest of the object intact.
You can mock the class that you are testing and specify the method that you want to mock.
$mock = $this->getMockBuilder('MyClass')
->setMethods(array('handleValue'))
->getMock();
$mock->expects($this->once())
->method('handleValue')
->will($this->returnValue(23)) //Whatever value you want to return
However, IMO this is not the best idea for your tests. Testing like this will make refactoring much more difficult. You are specifying the implementation of the class rather than the behavior that the class is supposed to have. If handleValue is doing a lot of complicated work that makes testing difficult, consider moving the logic into a separate class and injecting that into your class. Then you can create a mock of that class and pass it in to testMethod. Doing so will give you the added advantage of making MyClass more extensible if handleValue needs to adapt its behavior.
http://www.oodesign.com/strategy-pattern.html
As a general rule, you should not mock the system that you are testing.
You can specify which methods to mock (partial mock) with setMethods():
// Let's do a `partial mock` of the object. By passing in an array of methods to `setMethods`
// we are telling PHPUnit to only mock the methods we specify, in this case `handleValue()`.
$csc = $this->getMockBuilder('Lightmaker\CloudSearchBundle\Controller\CloudSearchController')
->setConstructorArgs($constructor)
->setMethods(array('handleValue'))
->getMock();
// Tell the `handleValue` method to return 'bla'
$csc->expects($this->any())
->method('handleValue')
->with('bla');
Any other methods in the class not specified in the array you give setMethods() will be executed as is. If you do not use setMethods all methods will return NULL unless you specifically set them.

Pass by reference in a callback when mocking in PHPUnit

I have an interface I want to mock, and mock the behaviour of one of it's methods.
So I have created a callback that mocks the behaviour very simply.
This test passes if I create a new object that is based on this interface, but I would like to mock the interface.
The mocked setUp method is being called fine, and calling getVar('testing') in my callback returns the value. However my assertion fails, because that value isn't available.
It seems that you can't do this in PHPUnit? Unless I am being stupid.
Brief explanation of the code flow; The code in "getVar" calls a method which calls the "setUp" on the added plugin. When it calls "setUp" it passes in "$this". It is $this I am expecting to be passed by reference and which works with a "real" object.
class DefaultRendererTest extends \PHPUnit_Framework_TestCase
{
public function testSetGetVar()
{
$theme = $this->getMock('ThemeInterface');
$plugin = $this->getMock('PluginInterface');
$plugin->expects($this->once())
->method('setUp')
->will($this->returnCallback(function($r){
$r->setVar('testing', "fooBar");
}));
$renderer = new DefaultRenderer($theme, null);
$renderer->addPlugin($plugin);
$this->assertEquals('fooBar',$renderer->getVar('testing'));
}
}
For info here is the interface, the DefaultRenderer implements a RendererInterface
interface PluginInterface
{
function setUp(RendererInterface $renderer);
}
OK, out of interest, I tracked down the issue. It seems that PHPUnit automatically clones the parameters before the actual invocation takes place. I don't see a real reason for this, but maybe there is one. Taking a look at Framework/MockObject/Invocation/Static.php, there is only a single way how you can avoid this (on basis of the built in mock code): Implement a private __clone() method in the DefaultRenderer.
I'd also suggest you ask on IRC or the PHPUnit mailinglist about this behaviour or the mock object library.

Categories