Laravel 8 Unit Testing - assertJson() how to capture it in controller - php

I have a laravel 8 Unit Test which looks like this:
public function testAddingTwoCars()
{
$response = $this->postJson('api/basket', ['cars' => [['name' => 'car one'], ['name' => 'car two']]]);
$response
->assertStatus(200)
->assertJson(['total' => 40]);
}
In my route I have:
Route::post('/api/basket',[basketController::class, 'store']);
In my controller I have:
public function store(Request $request)
{
$data = $request->all();
return response()->json($data);
}
when I run the test with php artisan test it shows this:
• Tests\Unit\BasketTest > adding two cars
Unable to find JSON:
[{
"total": 40
}]
within response JSON:
[{
"cars": [
{
"name": "car one"
},
{
"name": "car two"
}
]
}].
How do I:grab the total as its not in $request so that I could do something like this in my controller:
$public function store(Request $request)
{
$data = $request->all();
$data['total'] = 40;
return response()->json($data);
}
The above works, but it is hard coded so is not the best way to do it

assertJson
Assert that the response contains the given JSON data:
$response->assertJson(array $data, $strict = false);
The assertJson method converts the response to an array and utilizes PHPUnit::assertArraySubset to verify that the given array exists within the JSON response returned by the application. So, if there are other properties in the JSON response, this test will still pass as long as the given fragment is present.
You can read about it at this link
https://laravel.com/docs/8.x/http-tests#assert-json
and Here
https://laravel.com/docs/5.4/http-tests#testing-json-apis
and you can check this video too
https://adamwathan.me/2016/11/16/the-only-json-assertion-youll-ever-need/

->assertJson(['total' => 40]) is non-sense; this will not pass unless the JSON has it:
return response()->json(['data' => $data, 'total' => 40]);

Related

Laravel Cache helper "driver" not implemented

I have a test that is calling a Controller, which literally handles a call to some logic service I mocked.
The test
public function test_it_can_list_all_folders()
{
$mockedLogicResponse = [
"id" => 1111,
"parent_id" => 2222,
"name" => "Some Name",
"children" => [
[
"id" => 3333,
"parent_id" => 5555,
"name" => "Ad-hoc"
],
[
"id" => 4444,
"parent_id" => 6666,
"name" => "Another thing"
]
]
];
Cache::shouldReceive('has')
->once()
->with('campaign_folders')
->andReturn(false);
$this->instance(
Logic::class,
Mockery::mock(Logic::class, function (MockInterface $mock) use ($mockedLogicResponse) {
$mock->shouldReceive('fetchData')
->once()
->andReturn($mockedLogicResponse);
})
);
// httpGet is just a wrapper for a call('GET', ..), it's tested and working fine
$response = $this->httpGet($route);
$response->assertOk(); //This goes well
$this->assertEquals($mockedLogicResponse, $response->json()); //This goes well too
}
Controller:
class LogicController extends Controller {
protected $logic;
public function __construct(Logic $logic)
{
$this->logic = $logic;
}
public function index(Request $request)
{
$id = $request->get('folder_id');
return $this->logic->fetchData($id);
}
}
Logic:
class Logic {
public function fetchData(string $id): array
{
if (Cache::has('folders')) {
return Cache::get('folders');
}
//This is returning correctly the data
$foldersList = $this->getFolders(...);
foreach ($foldersList[$folder['id']] as $folder) {
$res = [....];
// We perform some irrelevant logic
$children = ['children' => $res[$folder['id']]];
$fetchedFolders[] = array_merge($folder, $children);
}
Cache::put('folders', $fetchedFolders, 3600);
return $fetchedFolders;
}
}
The problem(s) are a few, for starters I'm receiving this:
Mockery\Exception\BadMethodCallException : Received Mockery_2_Illuminate_Cache_CacheManager::driver(), but no expectations were specified
It's good to point out that I am literally copying an example from the documentation here Laravel docs, so I can't see I'm missing any step.
Also, as the Cache is being called from the mocked logic, but the method is calling them (I dumped the result of the "has" Cache method)
As I also want to test (In another test) that the Cache::get() is begin called when requested the data for a second time, how can I clean the Cache (I set it for an hour), in order to test something like so:
Cache::shouldReceive('has')->twice();
Cache::shouldReceive('get')->once();
Cache::shouldReceive('put')->once();
Is there any step I am missing? If so, which ones?
UPDATE: After googling a bit, I found this solution, which in part solves my testing issues, but I'm concerned why the official documentation is not working, in order to use it instead of a custom solution.
Kind regards

Best way to store and load JSON from database in Laravel

I'm trying to store json into a db and load it back
I tried to store
{name: "John", age: 31, city: "New York"}
It stored correctly. I checked the db, it showed correctly.
{name: "John", age: 31, city: "New York"}
I kept getting on the view
"{name: \"John\", age: 31, city: \"New York\"}"
This is my code.
public function store()
{
$paste = new Paste;
$paste->uuid = Str::uuid()->toString();
$paste->data = trim(Request::get('data',''));
$paste->save();
return Redirect::to('/paste/'.$paste->uuid)->with('success', 'Created');
}
public function show($uuid)
{
$paste = Paste::where('uuid',$uuid)->first();
return response()->json($paste->data);
}
Any hints for me ?
Reproducible here
https://www.bunlongheng.com/paste
Try # 2
If I did this
public function show($uuid)
{
$paste = Paste::where('uuid',$uuid)->first();
return View::make('layouts.fe.pastes.show', get_defined_vars());
}
and in my view, I only have this 1 line
{!!$paste->data!!}
I get the same data as what I submitted now.
{name: "John", age: 31, city: "New York"}
BUT the browser detected it as text, not a response JSON which defeated the purpose of what I am trying to do.
Try # 3
public function show($uuid)
{
$paste = Paste::where('uuid',$uuid)->first();
return response()->json(stripslashes($paste->data));
}
result
"{name: \"John\", age: 31, city: \"New York\"}"
Try # 4
public function show($uuid)
{
$paste = Paste::where('uuid',$uuid)->first();
return View::make('layouts.fe.pastes.show', get_defined_vars());
}
view
{{ json_encode($paste->data, JSON_UNESCAPED_SLASHES) }}
result
"{name: \"John\", age: 31, city: \"New York\"}"
Try #5
I think the issue is lying on the storing ... not the loading and rendering.
I tried
return response()->json($paste);
My JSON parser detected it ...
{
"id": 11,
"status": 0,
"uuid": "0c40f97d-7d98-42c6-864e-71d3ed81eed3",
"name": "n6ou",
"password": "",
"expiration": "",
"type": "json",
"data": "{name: \"John\", age: 31, city: \"New York\"}",
"created_at": "2021-04-22T22:53:11.000000Z",
"updated_at": "2021-04-22T22:53:11.000000Z"
}
This is what I used to store
$paste->data = trim(Request::get('data',''));
$paste->save();
Try #6
For those of you that doubt my data/content
I've tried pasting the same line in Pastebin
It's cleaned, you can see below.
https://pastebin.com/raw/r9akUK1v
Database
In your database migrations add:
$table->json('data'); // Recommended. Supported in MySQL since version 5.7.8
or
$table->text('data');
The JSON column type is recommended as it allows you to do SQL queries on JSON data. See MySQL JSON Data Type
Model: Casting the Attribute
The next issue is that you need to be able to cast your data into a PHP array.
This is done by modifying the casts attribute in the model:
class Paste extends Model {
protected $casts = [
'data' => 'array'
];
}
See Array and JSON Casting for more information.
Now you can save data onto the attribute as a PHP array, and also assign it a PHP array.
$paste = Paste::first();
dump($paste); // Returns a PHP array
$paste->data = ['some-data' => 20, 'score' => 500];
$paste->save();
Internally, when it saves the data, it automatically would convert it into a JSON string and save it in the database in the correct format.
Store Method
When taking in input as JSON, it highly depends in how you want to pass the data,
1. Sending form data with JSON content type (recommended)
My recommendation is to send the entire data as JSON in the POST body like so:
Content-Type: application/json
Body:
{
"data": {
"name": "John",
"age": 31,
"city": "New York"
},
"someOtherField": "Hello!"
}
Your store() method should now be (I've also added validation code):
public function store()
{
$this->validate($request, [
'data' => ['required', 'array'],
'data.*.name' => ['required', 'string'],
'data.*.age' => ['required', 'int'],
'data.*.city' => ['required', 'string'],
]);
$paste = new Paste();
$paste->uuid = Str::uuid()->toString();
$paste->data = $request->post('data'); // No need to decode as it's already an array
$paste->save();
return Redirect::to("/paste/{$paste->uuid}")
->with('success', 'Created');
}
2. Sending form data with form params
If however you insist in sending data through query params or form params, note these can only send strings. Therefore you need to send an encoded version of the JSON string to persists data types, as follows:
Form Params:
- data: '{"name": "John", "age": 31, "city": "New York"}'
- someOtherField: "Hello!"
The store method will now look like this:
$this->validate($request, [
'data' => ['required', 'json'], // I'm unsure if data is required
]);
$data = json_decode($request->post('data'), true, JSON_THROW_ON_ERROR); // Needs to be decoded
// validate $data is correct
Validator::make($data, [
'name' => ['required', 'string'],
'age' => ['required', 'int'],
'city' => ['required', 'string'],
])->validate();
$paste = new Paste();
$paste->uuid = Str::uuid()->toString();
$paste->data = $data;
$paste->save();
return Redirect::to("/paste/{$paste->uuid}")
->with('success', 'Created');
Show Method
Your show method needs no changes:
public function show($uuid)
{
$paste = Paste::where('uuid', $uuid)->first();
return response()->json($paste->data);
}
1- Your column need to be of type json type
$table->json('data');
2- in your Model you need to cast your column to an array
protected $casts = ['data' => 'array'];
3- sending data value to your controller must be an array so you can use array Laravel validation on it:
[
'data' => 'required|array',
'data.*.name' => 'required'
....
]
4- when you store your data it will be parsed automatically and the same when you retrieve your data column it will be converted to an array
Using ->json() as the migration method to store JSON data (https://laravel.com/docs/8.x/migrations#column-method-json)
Refer to "Array & JSON Casting" (https://laravel.com/docs/8.x/eloquent-mutators#array-and-json-casting) for how do you prepare the data
I know the answer is not in paragraphs as others, but I like to make it simple and straight. Is it the best solution? No one can tell you that nor prove that? Is this method going to work, no one can tell you that nor prove that, but at least it boosts up your success rate. Let me know if there is anything else I could help with! Good Luck
If you want to store data as json on DB and restore it just do the following (I always use this way):
1- Add your data to array:
$data["name"] = "John";
$data["age"] = 31;
$data["city"] = "New York";
2- Encode the array and add it to the database (you can store it as text)
$encodedData = json_encode($data);
3- If you want to add nested json data just make your array nested array.
4- When you restore the data just use json_decode to decode it
Just add this to your Model.
protected $casts = [
'data' => 'object'
];
Then you can get in your view like this:
{{ $data->data->name }}

In Laravel, how can i return a specific *column* from a *table* inside my API Controller ? I'm using Laravel 5

Question
How can i return a specific column from a table [within the API Controller] ? I used pluck but it removed the column name from the output. I need the column name to be included as well. The $product table i refer(Referred in my code below) is just a table with product stuffs like name, price, discount rate etc..
Brief description
Present API response sample -
{
"data" : [
"Graham",
"Marina Philip",
"David Doomer",
],
"message" : "",
"success" : true
}
Expected response -
[
{
"name": "Graham",
},
{
"name": "Marina Philip",
}, {
"name": "David Doomer",
},
]
API Route from the APIController :
Route::resource('searchlist', 'API\SearchlistAPIController');
Index function from my SearchlistAPIController.php [Specific function]
public function index(Request $request)
{
try{
$this->productRepository->pushCriteria(new RequestCriteria($request));
$this->productRepository->pushCriteria(new LimitOffsetCriteria($request));
$this->productRepository->pushCriteria(new ProductsOfFieldsCriteria($request));
if($request->get('trending',null) == 'week'){
$this->productRepository->pushCriteria(new TrendingWeekCriteria($request));
}
else{
$this->productRepository->pushCriteria(new NearCriteria($request));
}
$products = $this->productRepository->all();
} catch (RepositoryException $e) {
return $this->sendError($e->getMessage());
}
//Here i've got the value of the table $Product with a bunch of columns from my database..
$sendinger = $products->pluck('name');
//I'm trying to filter the columns send here. But i lost the column name as well.
return $this->sendResponse($sendinger->toArray(),'');
}
Contents of my sendResponse method:
public function sendResponse($result, $message) { return Response::json(ResponseUtil::makeResponse($message, $result)); }
Also, how can i remove this from my Json response ? :
"message" : "",
"success" : true
Off the top of my head, I believe $products->pluck('name')->toArray() will create return an indexed array. E.g. [ "Graham", "Marina Philip", "David Doomer" ]
What you could do is...
$sendinger = $products->pluck('name')->map(function($name) {
return [ 'name' => $name ];
});
return $this->sendResponse($sendinger->toArray(),'');
More about mapping here: https://laravel.com/docs/7.x/collections#method-map
Not tested but should steer you in the right direction.
For your other question (removing "success" and "message" from the response)...
It appears you are using the Laravel Generator ResponseUtil class which is adding the additional parameters. See source here: https://github.com/InfyOmLabs/laravel-generator/blob/7.0/src/Utils/ResponseUtil.php
Simply replace the last line:
return $this->sendResponse($sendinger->toArray(),'');
with
return response()->json( $sendinger->toArray() );
and that should do the trick!

Laravel - Creating an object from a JSON array to save it in a SQL database

What I am trying to do is to send a JSON array (that was gotten from Guzzle) to my SQL database. I have gotten to the point where I am able to get the response and display the gotten JSON array on a webpage. The array is defined as the $data variable. The $data variable gets decoded using this:
$data = json_decode($response->getBody()->getContents());
This is able to get the JSON and decode it with no problem. The part I am stuck on is taking the $data variable, processing it and sending it to my database. From what I understand is that you are required to convert the JSON into an array and then send it to the database.
The JSON format is like this:
[{
"INTLDES": "2017-042Z",
"NORAD_CAT_ID": "42848",
"OBJECT_TYPE": "TBA",
"SATNAME": "OBJECT Z",
"COUNTRY": "TBD",
"LAUNCH": "2017-07-14",
"SITE": "TTMTR",
"DECAY": null,
"PERIOD": "96.52",
"INCLINATION": "97.61",
"APOGEE": "597",
"PERIGEE": "586",
"COMMENT": null,
"COMMENTCODE": null,
"RCSVALUE": "0",
"RCS_SIZE": null,
"FILE": "6242",
"LAUNCH_YEAR": "2017",
"LAUNCH_NUM": "42",
"LAUNCH_PIECE": "Z",
"CURRENT": "Y",
"OBJECT_NAME": "OBJECT Z",
"OBJECT_ID": "2017-042Z",
"OBJECT_NUMBER": "42848"
}]
My Satellite Model goes like this:
protected $fillable = [
'intldes',
'norad_cat_id',
'object_type',
'satname',
'country',
'launch',
'site',
'decay',
'period',
'inclination',
'apogee',
'perigee',
'comment',
'commentcode',
'rcsvalue',
'rcs_size',
'file',
'launch_year',
'launch_num',
'launch_piece',
'current',
'object_name',
'object_id',
'object_number'
];
My migrations file:
Schema::create('satellites', function (Blueprint $table) {
$table->increments('id');
$table->string('intldes');
$table->string('norad_cat_id');
$table->string('object_type');
$table->string('satname');
$table->string('country');
$table->string('launch')->nullable();
$table->string('site')->nullable();
$table->string('decay')->nullable();
$table->string('period')->nullable();
$table->string('inclination')->nullable();
$table->string('apogee')->nullable();
$table->string('perigee')->nullable();
$table->string('comment')->nullable();
$table->string('commentcode')->nullable();
$table->string('rcsvalue')->nullable();
$table->string('rcs_size')->nullable();
$table->string('file')->nullable();
$table->string('launch_year')->nullable();
$table->string('launch_num')->nullable();
$table->string('launch_piece')->nullable();
$table->string('current')->nullable();
$table->string('object_name');
$table->string('object_id');
$table->string('object_number');
$table->timestamps();
});
I tried making an $object array, which did not work.
TL;DR: I want to take the $data variable, which contains the decoded JSON and create something that allows it to get saved into my 'satellites' SQL database.
EDIT: Here is the full Satellite controller:
public function displayer(){
$api = new Client([
'base_uri' => 'https://www.space-track.org',
'cookies' => true,
]); $api->post('ajaxauth/login', [
'form_params' => [
'identity' => '#',
'password' => '#',
],
]);
$response = $api->get('basicspacedata/query/class/satcat/orderby/INTLDES%20desc/limit/5/metadata/false');
$data = json_decode($response->getBody()->getContents(), true);
$data = array_change_key_case($data, CASE_LOWER);
$model = Satellite::create($data);
dd($data);
}
It looks like your JSON key names match up nicely with your model attributes, with the exception of being capitalised.
Try mapping the data keys to lowercase and then creating your model instance.
Per #OmisakinOluwatobi suggestion, you can use pass true to json_decode to retrieve the data as an array.
Edit - I missed that your response data was an array of objects. The following update will iterate over the response data and create a new Satellite for each.
// Retrieve data from response
$data = json_decode($response->getBody()->getContents(), true);
// Iterate over response data
foreach ($data as $attributes) {
// Change attribute keys to lowercase
$attributes = array_change_key_case($attributes, CASE_LOWER);
// Create satellite model
Satellite::create($attributes);
}
It is actually as simple as $encoded = json_encode($request->your_array);. This $encoded will now be "savable" to sql database. When you later access the encoded data, you can use a JSON parser to convert back to a json array. Example using jQuery var your_array = $.parseJSON($response.body);

Using accessors and mutators in Laravel json responses

So I'm making an API that produces a json response instead of doing View::make('foo', compact('bar')).
With blade templating
My controller simply returns the Eloquent model for Users:
public function index()
{
return User::all();
}
protected function getFooAttribute()
{
return 'bar';
}
And my view will be able to use it, along with the foo attribute (which isn't a field in the user's table).
#foreach($users as $user)
<p>{{$user->name}} {{$user->foo}}</p>
#endforeach
With Angular JS + json response
However, now that I'm not using blade but rather grabbing the json and displaying it with Angular JS I'm not able to do this:
<p ng-repeat="user in users">{{user.name}} {{user.foo}}</p>
Is there a way to cleanly pack the json response such that I have:
[{"name": "John", "foo": "bar"}, ...]
Warning: I've never built an API before and I've only started programming/web dev last December. This is my attempt:
public function index()
{
$response = User::all();
foreach($response as $r)
{
$r->foo = $r->foo;
}
return $response;
}
Yeah there is, example:
return Response::json([User:all()], 200);
Usually you want more than that though..
return Response::json([
'error' => false,
'data' => User:all()
], 200);
200 is the HTTP Status Code.
To include the attributes you need to specify these attributes to automatically append onto the response in your model.
protected $appends = array('foo');
public function getFooAttribute()
{
return 'bar';
}

Categories