undefined variable when testing the store() using phpunit in laravel-4 - php

SOLVED
I have route that does a POST route towards store() in the controller.
I'm trying to test if the action is working properly.
Controller:
public function store() {
$d= Input::json()->all();
//May need to check here if authorized
$foo= new Foo;
$d = array();
$d['name'] = $d['name'];
$d['address'] = $d['address'];
$d['nickname'] = $d['nickname'];
if($foo->validate($d))
{
$new_foo= $foo->create($d);
return Response::json(Foo::where('id','=',$new_foo->id)->first(),200);
}
else
{
return Response::json($foo->errors(),400);
}
}
Now I'm trying to test this using a new class called FooTest.php
Here is the function i'm currently trying to do to make the check work:
public function testFooCreation()
{
$jsonString = '{"address": "82828282", "email": "test#gmail.com", "name":"Tester"}';
$json = json_decode($jsonString);
$this->client->request('POST', 'foo');
$this->assertEquals($json, $this->client->getResponse());
}
when I run phpunit in my cmd, I get an error stating that "name" is undefined. I know i'm not actually passing anything to the request so I'm positive that nothing is actually being checked, but my question is how do I actually pass my json strings to check?
Everytime I put the $json inside the client request, it asks for an array, but when I convert my json string to an array, json_decode wants a string.
UPDATE
I was messing around with the passing of input data and I came across this:
$input = [
'name' => 'TESTNAME',
'address' => '299 TESTville',
'nickname' => 't'
];
Input::replace($input);
Auth::shouldReceive('attempt')
->with(array('name' => Input::get('name'),
'address' => Input::get('address'),
'nickname' => Input::get('nickname')))
->once()
->andReturn(true);
$response = $this->call('POST', 'foo', $input);
$content = $response->getContent();
$data = json_decode($response->getContent());
But whenever I run the test, i still get "name:undefined" It's still not passing the input i've created.

$d= Input::json()->all();
The above statement gets Input in $d.
$d = array();
Now the last statement again initialises $d as an empty new array.
So there is no: $['name'] . Hence, Undefined.
I think, that's the problem with the above code.
Hope it helps :)

I was able to pass the input into a POST route from the test.
public function testFooCreation(){
$json = '{"name":"Bar", "address":"FooLand", "nickname":"foobar"}';
$post = $this->action('POST', 'FooController#store', null, array(), array(), array(), $json);
if($this->assertTrue($this->client->getResponse()->isOk()) == true && $this->assertResponseStatus(201)){
echo "Test passed";
}
}
Turns out that in order for me to actually pass input into the controller through test POST, I have to pass it through the 7th parameter.
I hope this helps others.

of course you get an error , just look at your code
$aInputs = Input::json()->all();
//May need to check here if authorized
$foo= new Foo;
$d = array();
$d['name'] = $d['name'];
$d['address'] = $d['address'];
$d['nickname'] = $d['nickname'];
your assigning the array to it self, which is empty

Related

Mocking data and Calling action from container

I'm having issues making a Unit Testing because I have this code helperController Controller:
public static function applyDiscount(array $params, MoneyParser $moneyParser): Money
{
$discount = Money::BRL(0);
switch ($params['typeOf']) {
case 'above_value':
$amount = $params['value']->subtract($params['value']->multiply(0.85));
$discount = $discount->add($amount);
break;
case 'above_quantity':
$subtractedQuantity = intval(round($params['quantity'] / 3));
$value = $params['value']->multiply($subtractedQuantity);
$discount = $discount->add($value);
break;
case 'same_category':
$lowestVal = 0;
foreach ($params['cart'] as $item) {
if (empty($lowestVal)) {
$lowestVal = $item['unitPrice'];
} elseif ($item['unitPrice'] < $lowestVal) {
$lowestVal = $item['unitPrice'];
}
}
$valueOf = floatval($lowestVal) * 0.40;
$rounded = floor($valueOf * 100) / 100;
$unitPrice = $moneyParser->parse(strval($rounded), 'BRL');
$discount = $discount->add($unitPrice);
break;
case 'for_employees':
$amount = $params['value']->subtract($params['value']->multiply(0.80));
$discount = $discount->add($amount);
break;
case 'for_newones':
$amount = $moneyParser->parse('25', 'BRL');
$discount = $discount->add($amount);
break;
}
return $discount;
}
And I'm trying to mock the data to test it, for example, in the first case I'm trying to do this:
public function testApplyDiscountAboveValue(): void
{
$params = [
'typeOf' => 'above_value',
'value' => Money::BRL('3001'),
];
$money = app(MoneyParser::class);
$currency = app(Currency::class, ['code' => 'BRL']);
$moneyValue = app(Money::class, [
'amount' => 3001,
'currency' => $currency
]);
$discount = app(Money::class, [
'amount' => 450,
'currency' => $currency
]);
$mock = Mockery::mock($moneyValue);
$mock->shouldReceive('multiply')
->with(0.85)
->once()
->andReturn(Money::BRL('2550'));
$mock->shouldReceive('subtract')
->with(Money::BRL('2550'))
->once()
->andReturn(Money::BRL('450'));
$mock->shouldReceive('add')
->with(Money::BRL('450'))
->once()
->andReturn(Money::BRL('450'));
$response = app(HelperDiscountController::class)->applyDiscount($params, $money);
$this->assertEquals($discount, $response);
}
When I execute the phpunit my result is this:
App\Http\Controllers\HelperDiscountControllerTest::testApplyDiscountAboveValue
Mockery\Exception\InvalidCountException: Method multiply(0.85) from Mockery_0_Money_Money_Money_Money should be called
exactly 1 times but called 0 times.
I'm trying to understand what I am doing wrong. Did I have all the necessary adaptations to test my code at least for the first case?
First of all, if you have a static method in a controller, you are doing something wrong, it should be a non-static method, like this:
public function applyDiscount(array $params, MoneyParser $moneyParser): Money
{
// ...
}
Second, if the method applyDiscount is a helper method inside a controller or a parent controller, I would not pass an array $params, but directly a Request $request and use that object, because the array could have missing indexes and you should constantly validate them, with a Request object you can directly do $request->has('param') or do $request->input('param') (and pass a second parameter if you want a default value different from null if param is not present on the request as a parameter).
I do see that, for example, $params['value'] is an object, because you are doing operations on it, so if $params is not a parameter (you will never get an object from a Request as a parameter) then rename the variable to something else that truly repesent what it has, or at least, pass the content as separte method's parameters instead of an array of objects or similar.
Third, now onto the test. $response = app(HelperDiscountController::class)->applyDiscount($params, $money); will never work (get the mock), because you are never passing that mock into the Service Cointaner or into an instance as a dependency, you are creating the mock and not using it anywhere.
The code is a little bit confusing for me right now, but what you should have is something like this:
Controller
public function applyDiscount(Request $request, ): Money
{
// Validate or use a Form Request
// Do anything else you would need to
$result = $this->helperMethod(... needed params);
// Either return the result or process more stuff and return something if needed
return $result;
}
Test
public function testApplyDiscountAboveValue(): void
{
// When you need to mock something, you do it this way
$mock = $this->mock(WhateverClass::class);
$mock->shouldReceive('multiply')
->with(0.85)
->once()
->andReturn(Money::BRL('2550'));
$mock->shouldReceive('subtract')
->with(Money::BRL('2550'))
->once()
->andReturn(Money::BRL('450'));
$mock->shouldReceive('add')
->with(Money::BRL('450'))
->once()
->andReturn(Money::BRL('450'));
$response = $this->post(
'your/url/to/the/controller/endpoint',
[
'param1' => 'data1',
'paramN' => 'dataN',
],
);
$this->assertEquals($discount, $response);
}
See that:
When you use $this->mock(Class), it is internally binding that class resolution (when you do app(class) or resolve(class) or you let Laravel use Dependency Injection), it will return that mock
I am calling an endpoint using $this->post(url, data), you can use any HTTP method, but must match the route (that is how you test routes)
You need to share more info becausue it is very confusing (for me) what you are trying to test
Check my StackOverflow profile, I have some links in my profile about testing, it will help you a lot
Check the documentation as it is already explained in there most of the things you are trying to do
HTTP test
Mocking
Automatic Resolving/Dependency Injection

How to make a PHP function with array of arguments

Hi I'm new with PHP usually I work with Java, how actually the right way to write an array of many arguments/parameters in this insertSQL function as I have a lot SQL objects have to be inserted. Thank you
//Store User into MySQL DB
$res = $db->insertSQL(
$data[$i]->id,
$data[$i]->location_id,
$data[$i]->section_id,
$data[$i]->inspection_date,
$data[$i]->photo_entire_path,
$data[$i]->photo_isolator_path,
$data[$i]->pole_no,
$data[$i]->pole_iron,
$data[$i]->pole_iron,
$data[$i]->pole_iron);
//Based on inserttion, create JSON response
if($res){
$b["id"] = $data[$i]->id;
$b["status"] = 'yes';
array_push($a,$b);
}else{
$b["id"] = $data[$i]->id;
$b["status"] = 'no';
array_push($a,$b);
}
Right now it looks like this
$res = $db->insertSQL(
$data[$i]->id,
$data[$i]->location_id,
$data[$i]->section_id,
$data[$i]->inspection_date,
$data[$i]->photo_entire_path,
$data[$i]->photo_isolator_path,
$data[$i]->pole_no,
$data[$i]->pole_iron,
$data[$i]->pole_concrete,
$data[$i]->pole_wood,
$data[$i]->pole_condition_broken,
$data[$i]->pole_condition_tilt,
$data[$i]->pole_condition_shift,
$data[$i]->cros_arm_twist,
$data[$i]->cross_arm_rust,
$data[$i]->cross_arm_tilt,
$data[$i]->arm_tie_repair,
$data[$i]->arm_tie_rust,
$data[$i]->arm_tie_brace,
$data[$i]->isolator_fulcrum_r_leak,
$data[$i]->isolator_fulcrum_r_broken,
$data[$i]->isolator_fulcrum_s_leak,
$data[$i]->isolator_fulcrum_s_broken,
$data[$i]->isolator_fulcrum_t_leak,
$data[$i]->isolator_fulcrum_t_broken,
$data[$i]->isolator_pull_r_leak,
$data[$i]->isolator_pull_r_broken,
$data[$i]->isolator_pull_s_leak,
$data[$i]->isolator_pull_s_broken,
$data[$i]->isolator_pull_t_leak,
$data[$i]->isolator_pull_t_broken,
$data[$i]->arrester_r_broken,
$data[$i]->arrester_s_broken,
$data[$i]->arrester_t_broken,
$data[$i]->conductor_r_buyer,
$data[$i]->conductor_r_loose,
$data[$i]->conductor_s_buyer,
$data[$i]->conductor_s_loose,
$data[$i]->conductor_t_buyer,
$data[$i]->conductor_t_loose,
$data[$i]->connector_pg_r_35mm,
$data[$i]->connector_pg_r_70mm,
$data[$i]->connector_pg_r_150mm,
$data[$i]->connector_pg_s_35mm,
$data[$i]->connector_pg_s_70mm,
$data[$i]->connector_pg_s_150mm,
$data[$i]->connector_pg_t_35mm,
$data[$i]->connector_pg_t_70mm,
$data[$i]->connector_pg_t_150mm,
$data[$i]->bending_wire_r,
$data[$i]->bending_wire_s,
$data[$i]->bending_wire_t,
$data[$i]->ultrasonic_r,
$data[$i]->ultrasonic_s,
$data[$i]->ultrasonic_t,
$data[$i]->gws_exist,
$data[$i]->gws_not_exist,
$data[$i]->tree_exist,
$data[$i]->tree_not_exist,
$data[$i]->longitude,
$data[$i]->latitude,
$data[$i]->suggestion,
$data[$i]->descr
);
If I understand you correctly, this is what your function should look like:
function insertSQL(Array $sqlData)
{
Extract the values from the $sqlData variable here.
}
Try the following solution using call_user_func_array, func_get_args, json_encode and json_decode functions (to deal with an array of arguments):
...
$encoded = json_encode($data[$i]);
$fields_arr = json_decode($encoded, true); // to get an array of object properties with values.
// Also you should, probably, check the order of fields
$fields_arr['id'] = false;
$fields_arr = array_filter($fields_arr); // for removing the 'id' field
$args = ['id' => $data[$i]->id]; // first argument
call_user_func_array(array($db, 'insetSQL'), array_merge($args, $fields_arr)); // it also may require your current Namespace in the first arg
...
// Then, the 'insetSQL' method should process the arguments in the following manner:
function insetSQL($fields = []) {
$fields = func_get_args();
$id = $fields[0];
// adjusting the rest of fields ($fields[1], $fields[2] ...)
...
}
Although, I also would suggest to pass the initial object $data[$i] right into the insetSQL method as parameter and get the needed fields for sql INSERT statement
Create a JSON object as
$named_array = array(
"longitude" => "12.2"
"latitude" => "12.2"
);
$named_array = json_encode($named_array);
This $named_array can be array of fields.
You can pass $named_array object to other function as an argument.
Then use
$named_array = json_decode($named_array)
echo $named_array->longitude
You can access any Key Value pair as $named_array->latitude

Codeigniter passing objects within views

I am loading a view from a Controller file and that View loads another view which a final one as below,
First view Call :
Controller: device.php
public function device_name(){
$data = new stdClass;
$data->device_name = "Apple";
$this->load->view('apple_device',$data);
}
Second view call :
View: In apple_device.php
$device_name->count = 123;
$this->load->view('device_counts',$device_name);
I am using object here instead of an array as a passing variable between views. But if i use array, it works fine.
And the above code throwing error as like below,
Message: Attempt to assign property of non-object
Any help would be appreciated.
Yes, you may still pass through objects, but not at the 'first level', you'll need to wrap the object you want to pass through inside an array.
public function device_name(){
$mobiles = new stdClass;
$mobiles->device_name = "Apple";
$data = array( "mobiles" => $mobiles );
$this->load->view('apple_device',$data);
}
This is because when CodeIgniter will initialize the view, it will check the contents of the second view() parameter. If it's an object - it'll cast it to an array via get_object_vars() (See github link)
protected function _ci_object_to_array($object)
{
return is_object($object) ? get_object_vars($object) : $object;
}
Which will in turn, turn your initial $data into:
$data = new stdClass;
$data->device_name = "Apple";
$example = get_object_vars( $data );
print_r( $example );
Array ( [device_name] => Apple )
Thus to avoid this, nest your object inside an array() which will avoid being converted.

How to create data and return properly formatted json using ApiGility and RPC

I am using the RPC service of ApiGilty to return some data. I would like to double check whether or not this is the correct way of formatting and returning the data as I am not 100% sure of the correct process.
EDIT: To clarify
The data is being built from a number of entities:
main
main_extra
main_data
main_data_days
main_data_tiers
Is there a way to hit main and get all the sub entities? Currently I am building my data from scratch and returning an array.
My RPC Controller is as follows:
use My\Data\Controller\DataInterface;
use Zend\Mvc\Controller\AbstractActionController;
use ZF\ContentNegotiation\ViewModel;
class MyDataController extends AbstractActionController
{
const GENERAL_ERROR = 'api.rpc.my-data.my-data-controller';
public function __construct(
MyDataInterface $myData
)
{
$this->myData = $myData;
}
public function myDataAction()
{
$my_id = (int) $this->params()->fromRoute('my_id', 0);
if ($my_id == 0)
{
$data = $this->myData->getMyData();
} else
{
$data = $this->myData->getMyData($my_id);
}
$result = new ViewModel(array(
'data' => $data
));
return $result;
}
}
Now to create the data I am doing something like this:
public function getMyData( $my_id = null )
{
$returnArray = [];
$array1 = [
'key_1' => [1,2,3,4],
'key_2' => '123',
'key_3' => ['a','b','c']
];
$array2 = [
'key_1' => [1,2,3,4,5,6,7,8],
'key_2' => '123456',
'key_3' => ['a','b','c','d']
];
if ($my_id == 1) {
$array3 = ['some','or','other'];
} else {$array3 = []; }
$final_array = [
'data1' => $array1,
'data2' => $array2,
'data3' => $array3
];
$returnArray['data'] = $final_array;
$returnArray['success'] = 'true';
$returnArray['reason'] = '';
return $returnArray;
}
When checking with postman, I get the following:
Now since I have nothing to reference this against, my question is simply. Have I gone about this in the correct way and is this how the return code should be formatted?
Thanks!
Right now the Hal plugin is not used to render your result? You are responding a custom json object. Is this really what you want?
The response you currently return is not formatted according to HAL specifications. A proper HAL response should hold at least a _links key with a self href. It would be wrong to return this result with Content-Type headers set to application/hal+json. You should use application/json instead.
Here you can find documentation on how to respond HAL from an RPC-contoller.
I am not sure what you want to achieve but maybe you can be a bit more specific in your question so others can help out...
Doesn't look too bad, perhaps adhere to a standard such as jsend http://labs.omniti.com/labs/jsend or you could use hal-json, matthew weier o'phinney has a good blog post on this https://mwop.net/blog/2014-03-26-apigility-rpc-with-hal.html
Also you don't need to return a view model as you can just return an array and apigility will return JSON. You could also write a jsendViewModel if you go down that route.
Not exactly an answer but hope this helps you!

PHP : How to customing URL

Maybe this is an easy question.
I have a variable which save the user id in it.
$user_id = $_REQUEST['user_id'];
and then I have the URL like this :
try
{
$response = $client->delete('admin/user/**USER ID SHOULD HERE**',[
'headers' => ['Authorization' => $_SESSION['login']['apiKey']]
]);
}
I already try to put variable $user_id like this admin/user/$user_id in that URL but nothing happens.'
This is the delete method()
public function delete($url = null, array $options = [])
{
return $this->send($this->createRequest('DELETE', $url, $options));
}
Am I wrote something wrong ?
Thanks :)
PHP variables will not be parsed inside of a single quoted string. You should use "admin/user/$user_id" if you want the variable's value to be used.
So you could write it like this:
$response = $client->delete("admin/user/$user_id",[
Or simply by concatenating the string and user id variable using .:
$response = $client->delete('admin/user/'.$user_id,[

Categories