Dynamic contructor values for unit test in php - php

I am currently in the process of doing some refactoring. The stuff is not done by me. I just have to deal with it.
$expected = new instance(0,0,Argument::any());
$result = $this->otherInstance->returnsInstance([]);
$this->assertEquals($expected, $result);
Instance is some kind of model, which is returned by otherInstance. The problem is that the third argument is dynamic and an integer. It can be anything. As you can see, it is mandatory for instantiation of the model. Can this be mocked somehow? How do I set up the test properly?
This does obviously not work ...
::__construct() must be of the type integer, object given
So, how do I mock this? Or how do I set up the test in such a way as to handle dynamic values? The language level is 7.1, but I want to move to 7.4 soon.

One way to work with different test scenarios is using Data Providers.
In the example below, you set some arbitrary values in the method provideModelConstructorArguments. The test testMyTest will run twice, one time for each set of values present in the data provider.
public function provideModelConstructorArguments()
{
return [
[
'argument1' => 0,
'argument2' => 1,
'argument3' => 100
],
[
'argument1' => 5,
'argument2' => 3,
'argument3' => 57
]
];
}
/**
* #dataProvider provideModelConstructorArguments
**/
public function testMyTest($argument1, $argument2, $argument3)
{
$expected = new instance($argument1, $argument2, $argument3);
//continue your test code
}

Related

How can I test time dependend services? (Mocking DateTime)

given that I have a class like this one:
class DefaultProductContainer
{
public function __invoke(array $data, array $mandant): Product
{
$date = new \DateTime('now');
$metaData = [
"createdAt" => '',
"updatedAt" => $date->format('Y-m-d H:i:s.u'),
"status" => ['de'],
];
...
return Product::fromArray($params);
}
....
I want to test, if the created object is equal to what I expect.
But of course they differ, because the time they have been created differs.
Now I know from javascript how to test it with Faketimers. See How do I set a mock date in Jest?
Which is quite neat, because it enables to make such code testable, without changing the original code. And has many more advantages, when we are talking about date-calculations.
I searched now for more than two hours. Is there anything similar for PHP?

Elegently handle property of non-object errors that surface from different locations in code

My app reads from a DB that get's written by another API, now in some outlandish cases (that actually happened today) it wrote a customer id of 0, which ofcourse, does not exist.
I am looking for an elegant 'from-the-top' model or even presenter solution for handling erroneous ID's that do not exist.
So instead of finding every $whatever->customer->id in my app and then writing in an isset()/empty() ternary function, I am looking to pacify this error in a more elegant way where any customer instantiation/eloquent object would send the string "NA" to a non existent object, so even if an email/phone/etc or any other column of customer model, it would return a simple "NA" string.
I am struggling to find an eloquent solution that would provide 1 point of change.
you can use withDefault() modifier on your relationship.
example:
use Illuminate\Database\Eloquent\Model;
class Whatever extends Model {
public function customer() {
return $this->belongsTo(Customer::class, 'customer_id', 'id')
->withDefault([
'id' => 'NA',
'name' => 'Unknown'
// etc
]);
}
}
I would suggest you take a look at a Laravel class that most people don't know about. That is Fluent.
It allows you to do stuff like this:
$fluent = new Fluent([
'one' => 1,
'two => 2,
]);
echo $fluent->get('one'); // returns 1
echo $fluent->get('three'); // returns null
echo $fluent->get('three', 3); // returns 3
As you can imagine, it's perfect to use with third-party APIs and data that sometimes provide unexpected results. You can also do a lot more with Fluent.
Alternatively, you could look into Laravel helpers such as array_get(). From the documentation:
The array_get function retrieves a value from a deeply nested array using "dot" notation:
$array = ['products' => ['desk' => ['price' => 100]]];
$price = array_get($array, 'products.desk.price');
// 100
The array_get function also accepts a default value, which will be returned if the specific key is not found:
$discount = array_get($array, 'products.desk.discount', 0);
// 0

Recall chained methods on PHP

I call an object that returns an array given certain chained methods:
Songs::duration('>', 2)->artist('Unknown')->genre('Metal')->stars(5)->getAllAsArray();
The problem lies that every time I want to get this array, for example, in another script, I have to chain everything again. Now imagine that in over 10 scripts.
Is there a way to recall the chained methods for later use?
Since you can't cache the result, you could cache the structure of the call chain in an array.
$chain = [
'duration' => ['>', 2],
'artist' => 'Unknown',
'genre' => 'Metal',
'stars' => 5,
'getAllAsArray' => null
];
You could use that with a function that emulates the chained call using the cached array:
function callChain($object, $chain) {
foreach ($chain as $method => $params) {
$params = is_array($params) ? $params : (array) $params;
$object = call_user_func_array([$object, $method], $params);
}
return $object;
}
$result = callChain('Songs', $chain);
If you can not cache your results as suggested, as I commented, here are a couple ideas. If your application allows for mixing of functions (as in you are permitted by standards of your company's development rules) and classes, you can use a function wrapper:
// The function can be as complex as you want
// You can make '>', 2 args too if they are going to be different all the time
function getArtists($array)
{
return \Songs::duration('>', 2)->artist($array[0])->genre($array[1])->stars($array[2])->getAllAsArray();
}
print_r(getArtists(array('Unkown','Metal',5)));
If you are only allowed to use classes and __callStatic() is not forbidden in your development and is also available in the version of PHP you are using, you might try that:
// If you have access to the Songs class
public __callStatic($name,$args=false)
{
// This should explode your method name
// so you have two important elements of your chain
// Unknown_Metal() should produce "Unknown" and "Metal" as key 0 and 1
$settings = explode("_",$name);
// Args should be in an array, so if you have 1 value, should be in key 0
$stars = (isset($args[0]))? $args[0] : 5;
// return the contents
return self::duration('>', 2)->artist($settings[0])->genre($settings[1])->stars($stars)->getAllAsArray();
}
This should return the same as your chain:
print_r(\Songs::Unknown_Metal(5));
It should be noted that overloading is hard to follow because there is no concrete method called Unknown_Metal so it's harder to debug. Also note I have not tested this particular set-up out locally, but I have notated what should happen where.
If those are not allowed, I would then make a method to shorten that chain:
public function getArtists($array)
{
// Note, '>', 2 can be args too, I just didn't add them
return self::duration('>', 2)->artist($array[0])->genre($array[1])->stars($array[2])->getAllAsArray();
}
print_r(\Songs::getArtists(array('Unkown','Metal',5)));
I wrote a lib doing exactly what you're looking for, implementing the principle suggested by Don't Panic in a high quality way: https://packagist.org/packages/jclaveau/php-deferred-callchain
In your case you would code
$search = DeferredCallChain::new_(Songs::class) // or shorter: later(Songs::class)
->duration('>',2) // static syntax "::" cannot handle chaining sadly
->artist('Unknown')
->genre('Metal')
->stars(5)
->getAllAsArray();
print_r( $search($myFirstDBSongs) );
print_r( $search($mySecondDBSongs) );
Hoping it will match your needs!

How to show dirty values when debugging entity

I need use _get so I just did it at User entity just for test:
protected function _getName($name)
{
return $name . ' - FOOBAR';
}
So in the view I did Debug($user), and heres the result:
'properties' => [
'id' => (int) 32,
'name' => 'Daniel Pedro', //<- Clean Value
'email' => 'daniel#gmail.com',
],
'dirty' => [],
'original' => [],
'virtual' => [],
'errors' => [],
As you can notice the property name is with the original value Daniel Pedro, so I thought I did something wrong at _getName but when I look at the input at form the value was Daniel Pedro - FOOBAR.
My question is, how can I show the mutated values at Debug?
Debug the values separately
The most simple way to check the properties with their possible muatated values, is by extracting the visible properties, something like
debug($entity->extract($entity->visibleProperties()));
This won't include the ones that have been defined as "hidden" in the $_hidden property, if you need them too, then you'll have to explicitly include them
debug($entity->extract(array_merge($entity->visibleProperties(), $entity->hidden())));
Extend the debug info
If you'd wanted to somehow include this in the debug output of entities in general, then you'll have to overwrite the EntityTrait::__debugInfo() method and add the mutated properties in there.
Simple example, in your entity class (you can create a base entity class that all your entites extend so that you have this functionality in all entities):
public function __debugInfo()
{
$info = parent::__debugInfo();
$info['propertiesIncludingPossiblyMutatedValues'] =
$this->extract(array_keys($this->_properties));
return $info;
}
Or if you'd wanted to see only the ones that have really been mutated:
public function __debugInfo()
{
$info = parent::__debugInfo();
$info['mutated'] = array_diff(
$this->extract(array_keys($this->_properties)),
$this->_properties
);
return $info;
}
This should give you a hint of how things work.

Model Parameter Setup Best Practice?

I have a model that runs a query with a bunch of conditions in the SQL. As a result, the model needs to accept a lot of parameters, i.e:
this->model_name->method($param1, $param2, ... )
On the model side, I typically set this up as
function method($param1 = NULL, $param2 = NULL, ... )
Each of those parameters is optional, and use cases will vary around the app. So my question is: at what point (if ever) does it make sense to start passing these parameters to the method via an array, a la:
$params = [
'param1' => 'whatever',
'param2' => 'whatever',
...
]
this->model_name->method($params)
With the end goal being, I suppose, cleaner code, and less instances of method(null, null, null, null, $param) unless that's an okay thing to do.
Most answers have been supportive of the array method (which, generally speaking, I would also agree with), but I'll play devil's advocate and list some negatives:
Documentation is less clear
Most methods of documenting functions/methods will list the parameters of that function individually. For example, a function with a basic DocBlock will look like this:
/**
* A function that accepts an array of params
* #param array $param_array An array of key=>value arguments
*/
function accept_array($param_array = array('key1' => 'first_val', 'key2' => 'second_val')) {
var_dump($param_array);
}
Note how the DocBlock doesn't directly support individual parts of the $param_array, just the array as a whole. In contrast, listing all the arguments individually looks like this:
/**
* A function that 'normal' params
* #param string $key1 First argument
* #param string $key2 Second argument
*/
function accept_normal($key1 = 'first_val', $key2 = 'second_val') {
echo $key1;
echo $key2;
}
This is also a problem if you expect your functions to be fairly self-documenting, as in the first example you're not required to actually list your expected arguments in the function itself.
Default values may not work as expected
'As expected' is probably a bit of a loaded phrase (and this is probably one of the more obvious problems), but take the following:
function accept_array($param_array = array('key1' => 'first_val', 'key2' => 'second_val')) {
var_dump($param_array);
}
accept_array(array('key2' => 'a_different_val'));
Some may expect the var_dump to include the default value of key1 and the new value of key2, but the whole array is replaced, meaning you will need to remember to set default values for each key manually in each function, like so:
function accept_array($param_array = array()) {
if (!isset($param_array['key1'])) { $param_array['key1'] = 'first_val'; }
if (!isset($param_array['key2'])) { $param_array['key2'] = 'second_val'; }
var_dump($param_array);
}
accept_array(array('key2' => 'a_different_val'));
No automatic filtering
Listing the arguments the 'normal' way also gives you a built-in set of filters. Take for example this quick and dirty user search:
/**
* We want to allow searching for users by user_id or email only!
* #param array $param_array
*/
function find_user($param_array = array('user_id' => 0, 'email' => '')) {
foreach ($param_array as $field => $value) {
$this->db->or_where($field, $value);
}
$this->db->get('users');
}
find_user(array('first_name' => 'Joe', 'last_name' => 'Bloggs'));
Without manually adding some 'accepted keys' type validation on the $param_array, a call to the find_user() function can essentially use whatever fields it likes. The simpler version would obviously look like this:
/**
* We want to allow searching for users by user_id or email only!
* #param int $user_id
* #param string $email
*/
function find_user($user_id = 0, $email = '') {
$this->db->or_where('user_id', $user_id);
$this->db->or_where('email', $email);
$this->db->get('users');
}
// No way for me to submit any other fields, they'll just fail when they get to the query
find_user('Joe', 'Bloggs'));
I accept some of these are a bit entry-level and there's probably many more that I missed (feel free to comment with more and I'll copy them into the reply with credit), but hopefully there's enough there to make people think twice about automatically using the 'array method' without thinking about manual validation and documentation etc.
Passing an array of parameters provides a better option for self-documenting your code.
When I use many parameters, I often find myself using a style like:
// do_something_model($enable_option1,$enable_option2,$enable_option3)
do_something_model(FALSE, TRUE, FALSE)
where I carry a comment line with the parameter names to remind myself of how I am
using the model.
In such a case, using an array with meaningfully named keys provides a useful mnemonic.
More recently, I am also using more wrapper functions. For example, I may have my
basic model method do get all my data from a table and this method will have a few
options.
I then define a new method that does a specific task and then invoke the basic method within it using the correct options.
Footnote
I find that if my methods have "too many options", it is better to rethink the purpose of the method and to break it up into two or more specialized methods that are easier to use.
I would recommend the array version as well. Symfony2 also uses this pattern a lot, for instance in rendring templates, creating form classes and creating http responses in general. You just have to make sure you cleanly document all possible parameters.
You could go either route, but an array would definitely keep your methods cleaner. It makes perfect sense to pass the parameters as an array.

Categories