Testing a simple function in a model with Mockery - php

I'm totally new with Mockery which is embedded in Laravel. I have the pain to test a simple model function which increments a portion of a reference, whatever the value I'm passing to test the result is ok even when it should fail.
I think I made an error somewhere or I don't understand the documentation.
Thanks for your help.
Here is the simple function to test
public function incrementRefFormation(string $value):string
{
$split = str_split($value);
$group1 = '';
for ($i=0;$i<11;$i++) {
$group1 .= $split[$i];
}
$group2 = $split[11].$split[12];
$group2 = (int)$group2;
$group2++;
return $group1.$group2.$split[13];
}
Here is the test which should fail
public function testIncrementRefFormation()
{
//$testValue = '1 332 8100 20S';
$testValue = '123456';
$expectedValue = '1332810021S';
$mock = Mockery::mock('App\Models\Formation');
$mock->shouldReceive(['incrementRefFormation' => $expectedValue])
->once();
var_dump($mock->incrementRefFormation($testValue));
}
Many thanks!

Mockery is used for creating 'mocks', which are dumb objects doing only what you tell them to do (e.g. method x will return y if supplied with parameter z). Usually it's used for mocking dependencies of the class you want to test. In your case you probably won't need it.
So your test would probably look something like this
$formation = new App\Models\Formation();
$actualvalue = $formation->incrementRefFormation($testValue);
$this->assertEquals($expectedValue, $actualvalue);

Related

PHPUnit simple method to compare two dates

I'm not familiar with PHPUnit and just want to execute a simple method that return a DateTimeImmutable and compare it with another DateTimeImmutable.
public function testGetLunchTimesBeginHour()
{
$minLunchTime = \DateTimeImmutable::createFromFormat('H:i', self::MIN_BEGIN_LUNCH);
$maxLunchTime = \DateTimeImmutable::createFromFormat('H:i', self::MAX_END_LUNCH);
foreach($this->daysOfAppointments as $dayOfAppointments){
$appointments = $this->makeAppointmentsDatetime($dayOfAppointments);
$mock = $this->getMockBuilder(GetLunchTimesBeginHour::class)->getMock();
$expected = \DateTimeImmutable::createFromFormat('H:i', $dayOfAppointments['expected']);
$actualResult = $mock->expects($this->once())->method('getLunch')->with($appointments, $minLunchTime, $maxLunchTime, self::DURATION_LUNCH);
$this->assertEquals(
$expected,
$actualResult,
"unexpected result");
}
}
I understand the problem is that $actualResult is a PHPUnit\Framework\MockObject\Builder\InvocationMocker instead of a DateTimeImmutable.
How to just execute the method and get the result ? Must I call the real class instead of a mock ?
Well, I'm not sure what you want to test here, but here an example:
$expectedDate = new \DateTimeImmutable('2022-11-01');
$now = new \DateTimeImmutable('2022-10-01');
$myClass = new MyClass();
$newDate = $myClass->calcul($now);
// ± 5 seconds are equals
self::assertEqualsWithDelta($expectedDate->getTimestamp(), $newDate->getTimestamp(), 5);
And use a foreach in a unit-test is not a good practice, it's better to use a dataProvider
https://phpunit.readthedocs.io/en/9.5/writing-tests-for-phpunit.html#writing-tests-for-phpunit-data-providers
I would not mock what you are trying to test. Try something like this, you will need to fill in the gaps and mock any dependancies though. This is assuming that the method getLunch returns a dateTime object.
public function testGetLunchTimesBeginHour()
{
$minLunchTime = \DateTimeImmutable::createFromFormat('H:i', self::MIN_BEGIN_LUNCH);
$maxLunchTime = \DateTimeImmutable::createFromFormat('H:i', self::MAX_END_LUNCH);
//Class you are testing, you will need to mock the class dependancies and pass them in.
$testObject = new GetLunchTimesBeginHour();
foreach($this->daysOfAppointments as $dayOfAppointments){
$appointments = $this->makeAppointmentsDatetime($dayOfAppointments);
$mock = $this->getMockBuilder(GetLunchTimesBeginHour::class)->getMock();
$expected = \DateTimeImmutable::createFromFormat('H:i', $dayOfAppointments['expected']);
//get real response from created object you are testing.
$actualResult = $testObject->getLunch($appointments, $minLunchTime, $maxLunchTime, self::DURATION_LUNCH);
$this->assertEquals(
$expected,
$actualResult,
"unexpected result"
);
}
}

PHPUnit test function with value passed by reference and a returned value

Hi all I need to test a piece of code that call a function of another class that I can't edit now.
I need only to test It but the problem is that this function has a values passed by reference and a value returned, so I don't know how to mock It.
This is the function of column class:
public function functionWithValuePassedByReference(&$matches = null)
{
$regex = 'my regex';
return ($matches === null) ? preg_match($regex, $this->field) : preg_match($regex, $this->field, $matches);
}
This is the point where is called and where I need to mock:
$matches = [];
if ($column->functionWithValuePassedByReference($matches)) {
if (strtolower($matches['parameters']) == 'distinct') {
//my code
}
}
So I have tried
$this->columnMock = $this->createMock(Column::class);
$this->columnMock
->method('functionWithValuePassedByReference')
->willReturn(true);
If I do this return me error that index parameters doesn't exist obviously so I have tried this:
$this->columnMock = $this->createMock(Column::class);
$this->columnMock
->method('functionWithValuePassedByReference')
->with([])
->willReturn(true);
But same error, how can I mock that function?
Thanks
You can use ->willReturnCallback() to modify the argument and also return a value. So your mock would become like this:
$this->columnMock
->method('functionWithValuePassedByReference')
->with([])
->willReturnCallback(function(&$matches) {
$matches = 'foo';
return True;
});
In order for this to work, you will need to turn off cloning the mock's arguments when you build the mock. So your mock object would be built like so
$this->columnMock = $this->getMockBuilder('Column')
->setMethods(['functionWithValuePassedByReference'])
->disableArgumentCloning()
->getMock();
This really is code smell, btw. I realize that you stated that you can't change the code that you are mocking. But for other people looking at this question, doing this is causing side effects in your code and can be a source of very frustrating to fix bugs.

How do you mock a virtual binary file so that exec() / system() / passthru() function output can be tested?

I have an interesting problem and have searched the internet, but haven't yet found an answer.
I work for a company that doesn't allow it's workers to utilize OOP, it is kind of ridiculous, but the working experience is valuable.
Consider the following function:
function get_setting_values_from_file( $parameter )
{
exec("/usr/var/binary --options $parameter", $output, $return);
$settings = file( $output[0] );
foreach( $settings as $setting ) {
if( strstr( $setting, "color") ) {
$setting = explode( ":", $setting );
return $setting[1];
}
}
return false;
}
I need to unit test a similar function. I am currently using phpUnit for my tests and the vfsStream libraries to mock the file system, but how do you mock the call to exec("/usr/var/binary --options $parameter", $output, $return) when I'm developing with no access to the actual system? What is the recommend approach for dealing with test cases like this?
All feedback is appreciated.
You could mock exec() by using a function mock library. I made one (php-mock) for you which requires you to use namespaces
namespace foo;
use phpmock\phpunit\PHPMock;
class ExecTest extends \PHPUnit_Framework_TestCase
{
use PHPMock;
public function testExec()
{
$mock = $this->getFunctionMock(__NAMESPACE__, "exec");
$mock->expects($this->once())->willReturnCallback(
function ($command, &$output, &$return_var) {
$this->assertEquals("foo", $command);
$output = "failure";
$return_var = 1;
}
);
exec("foo", $output, $return_var);
$this->assertEquals("failure", $output);
$this->assertEquals(1, $return_var);
}
}
Simply mock this function to return the text that you are trying to get into $settings. You do not need to call the executable, simply create the file or return.
For instance, assuming the function get_setting_values_from_file() returns the settings as an array, you can simply mock the function in your test to return the settings as an array. Create a test stub to mock the object that contains the get_setting_values_from_file() method, and have that mock simply return the same FALSE, 1 or 2 that the test assumed.
$stub = $this->getMock('GetSettingsClass');
$stub->expects($this->any())
->method('get_settings_from_file')
->will($this->returnValue(0));
This is from the PHPUnit manual -> http://phpunit.de/manual/3.8/en/test-doubles.html#test-doubles.stubs
Optionally, you could even bypass the call, and simply test the functions/code that works on the returns by creating the array and passing it to those functions.
Assumed Example in the main code:
...
$settings = get_setting_values_from_file( 'UserType' );
$UserType = get_user_type($settings);
return $UserType;
function get_user_type($settings)
{
if($settings !== FALSE) // Returned from your function if parameter is not found
{
switch($settings)
{
case 1:
return 'User'; // Best to use Constants, but for example here only
break;
case 2:
return 'Admin';
break;
...
}
}
else
{
return FALSE;
}
}
Now, in your test, you can simply
$this->assertFalse(get_user_type(FALSE, 'Ensure not found data is handled properly as FALSE is returned');
$this->assertEqual('User', get_user_type(1), 'Test UserType=1');
$this->assertEqual('Admin', get_user_type(1), 'Test UserType=2');
...
These work as the code does not call the function that had to mock the read from the OS, but does handle all the expected returns by calling the function processing the setting return value. Here, you have simply assumed the return from the function 'get_setting_values_from_file()' without needing the file or any mocks.
This does NOT however test reading from the file, which I would do in another test by using the setUp and tearDown to actual create a file with the values you want (fopen/fwrite) and then call your function and ensure it returns what is expected.
I hope this helps to explain what I was thinking.

Is there any way to reset all static properties of a particular class?

Static properties make testing hard as you probably know. Is there no way to reset all static properties of a particular class back to their initial state? Ideally this would not require custom code for each class, but could be used in a general way by inheritance, or from outside of the class completely.
Please do not reply with something like, "don't use static properties". Thanks.
Assuming you're using PHPUnit:
See the PHPUnit Manual section about global state. Static members are covered by this if you have PHP 5.3 or higher. Static members are not part of serialization (in case you wonder).
See as well #backupGlobals and #backupStaticAttributes
No. PHP does not preserve that information.
I was toying around with ReflectionClass and ::getDefaultProperties and ::getStaticProperties, but they only return the current state.
You will have to create an array with the default values, then manually foreach over them and reset your class attributes.
I couldn't find any way to include or require classes or functions many times without getting an error.
Anyway, if you need to replace functions inside an structure you should make an array/ArrayObject of lamdas/inline functions (like javascript objects)
When you re import the array it will back to the original state.
$Animal = array(
'eat' => function($food) {/*...*/},
'run' => function($to_place) {/*...*/}
);
$Animal['eat'] = function($food) {/* new way to eat */}
I also managed to reset the state of static attributes by using Reflections. For this approach you need to use a convention attribute naming for default value of each type.
class MyStaticHolder {
public static $x_array = array();
public static $x_num = 0;
public static $x_str = '';
}
//change values
MyStaticHolder::$x_array = array(1,2,4);
MyStaticHolder::$x_num = -1.4;
MyStaticHolder::$x_str = 'sample-text';
function reset_static($class_name) {
$z = new ReflectionClass($class_name);
$properties = $z->getDefaultProperties();
print_r($properties);
foreach ($properties as $property_name => $value) {
$sufix = end(explode('_',$property_name));
switch ($sufix) {
case 'array':
$class_name::$$property_name = array();
break;
case 'num':
$class_name::$$property_name = 0;
break;
case 'str':
$class_name::$$property_name = '';
break;
default:
$class_name::$$property_name = null;
break;
}
}
}
reset_static('MyStaticHolder');

how do I override php://input when doing unit tests

I'm trying to write a unit test for a controller using Zend and PHPUnit
In the code I get data from php://input
$req = new Zend_Controller_Request_Http();
$data = $req->getRawBody();
My code works fine when I test the real application, but unless I can supply data as a raw http post, $data will always be blank. The getRawBody() method basically calls file_get_contents('php://input'), but how do I override this in order to supply the test data to my application.
I had the same problem and the way I fixed it was to have the 'php://input' string as a variable that is settable at run time. I know this does not really apply directly to this question as it would require modifying the Zend Framework. But all the same it may be helpful to someone.
For example:
<?php
class Foo {
public function read() {
return file_get_contents('php://input');
}
}
would become
<?php
class Foo {
public $_fileIn = 'php://input';
public function read() {
return file_get_contents($this->_fileIn);
}
}
Then in my unit test I can do:
<?php
$obj = new Foo();
$obj->_fileIn = 'my_input_data.dat';
assertTrue('foo=bar', $obj->read());
You could try mocking the object in your unit tests. Something like this:
$req = $this->getMock('Zend_Controller_Request_Http', array('getRawBody'));
$req->method('getRawBody')
->will($this->returnValue('raw_post_data_to_return'));
Provided the $req->getRawBody() is, as you say, the same as file_get_contents('php://input')...
$test = true; /* Set to TRUE when using Unit Tests */
$req = new Zend_Controller_Request_Http();
if( $test )
$data = file_get_contents( 'testfile.txt' );
else
$data = $req->getRawBody();
Not a perfect solution, but similar to what I have used in the past when designing scripts to handle piped emails with great success.
Zend_Controller_Request_HttpTestCase contains methods for setting and getting various http request/responses.
For example:
$req = new Zend_Controller_Request_HttpTestCase;
$req->setCookie('cookie', 'TRUE');
$test = $this->controller->cookieAction($req);
$this->assertSame($test, TRUE);

Categories