Unit testing with mocks and reflections in PHPUnit - php

I'm need to test protected method testMethod of class which uses some another method mockMethod. So I'm writing:
$class = $this->getMockBuilder('TestingClassName')
->setMethods(['mockMethod'])
->getMock();
$class->expects($this->any())
->method('mockMethod')
->will($this->returnValue('Hey!'));
$reflection = new \ReflectionObject($class);
$method = $reflection->getMethod('testMethod');
$method->setAccessible(true);
$result = $method->invokeArgs($class, $paramsArray);
But when method mockMethod invokes inside of testMethod it becomes like not mocked. It's protected method too and if I invoke it with another ReflectionMethod - I will give 'Hey!' from it.

Yes, the inner method was private, not protected.

Related

Mockery not able to call my method in testing method

I'm trying to write a test for a method in the class below. However, when I run the test I get the error that get_b64 is never run? I don't see how this is not running.
I've had a little look into the mockery documentation for testing static methods, but as far as I can tell this error isn't due to that?
What do I need to change with my testing strategy or be able to mock the function call in the mocked object?
Class:
namespace App\Services\Steam;
use App\Services\Steam\Utils;
class Steam
{
public function profile(string $steamID)
{
$b64 = Utils::get_b64($steamID);
if ($b64 === null) {
throw new \App\Exceptions\InvalidSteamId();
}
return new Profile($b64);
}
}
TestCase:
public function test_create_user_object()
{
$id = "123"
$utilsMock = Mockery::mock(\App\Services\Steam\Utils::class);
$utilsMock->shouldReceive('get_b64')
->once()
->with($id)
->andReturn($id);
$steam = new \App\Services\Steam\Steam();
$steam->profile($id);
}
You call get_b64 statically, which means it is called from the class, not an object.
To mock such calls you need to use aliases:
public function test_create_user_object()
{
$id = "123"
$utilsMock = Mockery::mock('alias:\App\Services\Steam\Utils');
$utilsMock->shouldReceive('get_b64')
->once()
->with($id)
->andReturn($id);
$steam = new \App\Services\Steam\Steam();
$steam->profile($id);
}
Bear in mind that it completely replaces the Utils class, so if you have more static functions called from the class, you need to mock them as well.

Mockery and Laravel constructor injection

I am using laravel 5 with php unit to create a laravel package. I have a Repository..
namespace Myname\Myapp\Repositories;
use Myname\Myapp\Models\PersonModel;
class PersonRepository
{
protected $personModel;
public function __construct(PersonModel $personModel)
{
$this->personModel = $personModel;
}
public function testFunction($var)
{
return $this->personModel->find($var);
}
}
..which implements a Model.
namespace Myname\Myapp\Models;
use Illuminate\Database\Eloquent\Model;
class PersonModel extends Model
{
protected $table = 'person';
}
Laravels IoC automatically injects PersonModel into the constructor of PersonRepository.
I am writing a unit test where I want to mock the PersonModel model using mockery so I am not hitting the database during testing.
namespace Myname\Myapptests\unit;
use Mockery;
class PersonRepositoryTest extends \Myname\Myapptests\TestCase
{
/**
* #test
*/
public function it_returns_the_test_find()
{
$mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
->shouldReceive('find')
->with('var');
$this->app->instance('Myname\Myapp\Models\PersonModel', $mock);
$repo = $this->app->make('Myname\Myapp\Repositories\PersonRepository');
$result = $repo->testFunction('var');
$this->assert...
}
}
When I run the test I get an error
1) Myname\Myapptests\unit\PersonRepositoryTest::it_returns_the_test_find
ErrorException: Argument 1 passed to Myname\Myapp\Repositories\PersonRepository::__construct() must be an instance of Myname\Myapp\Models\PersonModel, instance of Mockery\CompositeExpectation given
From what I have read, mockery extends the class it is mocking so there should be no issue injecting the extended class in place of the type hinted parent (PersonModel)
Obviously I am missing something. Other examples explicitly inject the mocked object into the class they are then testing. Laravels IoC is (should be) doing this for me. Do I have to bind anything?
I have a feeling though that the mockery object isn't being created in the way I think (extending PersonModel) otherwise I assume I wouldn't see this error.
Problem is when you create your mock:
$mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
->shouldReceive('find')
->with('var');
So this:
$mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
var_dump($mock);
die();
Will output something like this: Mockery_0_Myname_Myapp_Models_PersonModel
But this:
$mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
->shouldReceive('find')
->with('var');
var_dump($mock);
die();
Will output this: Mockery\CompositeExpectation
So try doing something like this:
$mock = Mockery::mock('Myname\Myapp\Models\PersonModel');
$mock->shouldReceive('find')->with('var');
$this->app->instance('Myname\Myapp\Models\PersonModel', $mock);
$repo = $this->app->make('Myname\Myapp\Repositories\PersonRepository');
$result = $repo->testFunction('var');
While Fabio gives a great answer, the issue here is really the test setup. Mockery's mock objects do comply to contracts and will pass instanceof tests and type hints in method arguments.
The problem with the original code is that the chain of methods being called end up returning an expectation rather than a mock. We should instead create a mock first, then add expectations to that mock.
To fix it, change this:
$mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
->shouldReceive('find')
->with('var');
Into this:
$mock = Mockery::mock('Myname\Myapp\Models\PersonModel');
$mock->shouldReceive('find')->with('var');
The variable $mock will now implement PersonModel.
Bonus:
Instead of 'Myname\Myapp\Models\PersonModel', use PersonModel::class. This is a lot more IDE-friendly and will help you when refactoring code later on.

PHPUnit: Mock a method to behave like identity

Using PHPUnit, is there any way to mock a method and make it behave like identity (x->x) ? Something around the lines of :
$this->myMethod(Argument::any())->willReturn(<should return the argument untouched>)
You can use the returnArgument() method. From the doc:
Sometimes you want to return one of the arguments of a method call
(unchanged) as the result of a stubbed method call. Example 9.4 shows
how you can achieve this using returnArgument() instead of
returnValue().
As Example:
class StubTest extends PHPUnit_Framework_TestCase
{
public function testReturnArgumentStub()
{
// Create a stub for the SomeClass class.
$stub = $this->getMockBuilder('SomeClass')
->getMock();
// Configure the stub.
$stub->method('doSomething')
->will($this->returnArgument(0));
// $stub->doSomething('foo') returns 'foo'
$this->assertEquals('foo', $stub->doSomething('foo'));
// $stub->doSomething('bar') returns 'bar'
$this->assertEquals('bar', $stub->doSomething('bar'));
}
}

PHPunit mocking call mocked method in same mocked object

I have question about mocking object...
I have class "Example", and I need to test callMethod()
public function callMethod() {
$item = 0;
foreach($this->returnSomething() as $list) {
$item = $item + $list->sum;
}
return $item;
}
I have test method where I mock "returnSomething" to return me some data, but problem is than It doesn't call mocked method.
This is part of test method where im mocking "returnSomething" and call "callMethod".
$mock = mock("Example");
$mock->shouldReceive("returnSomething")->once()->withNoArgs()->andReturn($returnItems);
$result = $mock->callMethod();
Is it possible to call mocked "returnSomething" without changing "callMethod" definition and forwarding $mock object into that method?
I am writing this because today I found the answer here, but setMethods() is deprecated (phpunit 8.5), the alternative is onlyMethods() and it can be used as follows:
$mock = $this->getMockBuilder(Example::class)
->onlyMethods(['yourOnlyMethodYouWantToMock'])
->getMock();
$mock->method('yourOnlyMethodYouWantToMock')
->withAnyParameters()
->willReturn($yourReturnValue);
It is possible to mock only specified method.
Examples:
Mockery:
$mock = \Mockery::mock("Example[returnSomething]");
PHPUnit:
$mock = $this->getMock('Example', array('returnSomething'));
or
$mock = $this->getMockBuilder('Example')
->setMethods(array('returnSomething'))
->getMock();
With above cases a framework will mock only returnSomething method and leave the rest methods as in original object.

phpunit - mockbuilder - set mock object internal property

Is it possible to create a mock object with disabled constructor and manually setted protected properties?
Here is an idiotic example:
class A {
protected $p;
public function __construct(){
$this->p = 1;
}
public function blah(){
if ($this->p == 2)
throw Exception();
}
}
class ATest extend bla_TestCase {
/**
#expectedException Exception
*/
public function testBlahShouldThrowExceptionBy2PValue(){
$mockA = $this->getMockBuilder('A')
->disableOriginalConstructor()
->getMock();
$mockA->p=2; //this won't work because p is protected, how to inject the p value?
$mockA->blah();
}
}
So I wanna inject the p value which is protected, so I can't. Should I define setter or IoC, or I can do this with phpunit?
You can make the property public by using Reflection, and then set the desired value:
$a = new A;
$reflection = new ReflectionClass($a);
$reflection_property = $reflection->getProperty('p');
$reflection_property->setAccessible(true);
$reflection_property->setValue($a, 2);
Anyway in your example you don't need to set p value for the Exception to be raised. You are using a mock for being able to take control over the object behaviour, without taking into account it's internals.
So, instead of setting p = 2 so an Exception is raised, you configure the mock to raise an Exception when the blah method is called:
$mockA = $this->getMockBuilder('A')
->disableOriginalConstructor()
->getMock();
$mockA->expects($this->any())
->method('blah')
->will($this->throwException(new Exception));
Last, it's strange that you're mocking the A class in the ATest. You usually mock the dependencies needed by the object you're testing.
Hope this helps.
Thought i'd leave a handy helper method that could be quickly copy and pasted here:
/**
* Sets a protected property on a given object via reflection
*
* #param $object - instance in which protected value is being modified
* #param $property - property on instance being modified
* #param $value - new value of the property being modified
*
* #return void
*/
public function setProtectedProperty($object, $property, $value)
{
$reflection = new ReflectionClass($object);
$reflection_property = $reflection->getProperty($property);
$reflection_property->setAccessible(true);
$reflection_property->setValue($object, $value);
}
Based on the accepted answer from #gontrollez, since we are using a mock builder we do not have the need to call new A; since we can use the class name instead.
$a = $this->getMockBuilder(A::class)
->disableOriginalConstructor()
->getMock();
$reflection = new ReflectionClass(A::class);
$reflection_property = $reflection->getProperty('p');
$reflection_property->setAccessible(true);
$reflection_property->setValue($a, 2);
Based on #rsahai91 answer above, created a new helper for making multiple methods accessible. Can be private or protected
/**
* Makes any properties (private/protected etc) accessible on a given object via reflection
*
* #param $object - instance in which properties are being modified
* #param array $properties - associative array ['propertyName' => 'propertyValue']
* #return void
* #throws ReflectionException
*/
public function setProperties($object, $properties)
{
$reflection = new ReflectionClass($object);
foreach ($properties as $name => $value) {
$reflection_property = $reflection->getProperty($name);
$reflection_property->setAccessible(true);
$reflection_property->setValue($object, $value);
}
}
Example use:
$mock = $this->createMock(MyClass::class);
$this->setProperties($mock, [
'propname1' => 'valueOfPrivateProp1',
'propname2' => 'valueOfPrivateProp2'
]);
It would be amazing if every codebase used DI and IoC, and never did stuff like this:
public function __construct(BlahClass $blah)
{
$this->protectedProperty = new FooClass($blah);
}
You can use a mock BlahClass in the constructor, sure, but then the constructor sets a protected property to something you CAN'T mock.
So you're probably thinking "Well refactor the constructor to take a FooClass instead of a BlahClass, then you don't have to instantiate the FooClass in the constructor, and you can put in a mock instead!" Well, you'd be right, if that didn't mean you would have to change every usage of the class in the entire codebase to give it a FooClass instead of a BlahClass.
Not every codebase is perfect, and sometimes you just need to get stuff done. And that means, yes, sometimes you need to break the "only test public APIs" rule.
You may want to use the ReflectionProperty as well.
$reflection = new ReflectionProperty(Foo::class, 'myProperty');
$reflection->setAccessible(true); // Do this if less than PHP8.1.
$reflection->setValue($yourMock, 'theValue');

Categories