phpunit getContent return empty - laravel-5

I am learning Laravel 5.4, Still new to Laravel and PHPUnit. Everything is working great after following online basic tutorial.
This test function is working correctly when run phpunit
public function testBasicExample()
{
$response = $this->call( 'GET' , '/welcome');
$this->assertTrue(strpos($response->getContent(), 'Laravel') !== false);
}
Problem comes when I try to test Api
Steps I took
Create Api route for books
Return all users from users talbe as json from localhost/api/books/
public function index()
{
$users = DB::table('users')->get()->toJson();
echo $users;
}
I open the link in browser and json is returned correctly
copy and pasted json into online json validator jsonlint and it is valid.
Create a new test function
public function test_index_method_returns_all_books()
{
$response = $this->call( 'GET' , '/api/books/');
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getContent(),true);
$this->assertJson($data);
}
run phpunit
200 status test passed but assertJson did not pass.
I tried to do var_dump for $response->getContent() and found out it return empty.
now I am not able to get getContent() for api/book/. Does anyone know if there is a solution for this?
Thanks.
Here is a screenshot

Try create some data with a factory before you call the api:
e.g.:
factory(\App\Books::class, 20)->create();
then
$response = $this->call( 'GET' , '/api/books/');
If you had set ":memory:" as your database on phpunit.xml, you will no longer see any data from your local database, that's why you should use factory instead.

Related

Trying to mock an http client that is inside a controller, using phpunit, but it doesn't work

I have to test the routes of a controller which uses Guzzle Wrapper as a client to get data from an API. The app uses Laravel as a framework.
This is the part of the code that gives me trouble in the controller's function I am currently testing:
public function addContacts(Request $request)
{
...
$client = new GuzzleWrapper();
$response = $client->post($uri, $data);
if($response->getStatusCode() != 200) {
return response()->json("Problem getting data", 500);
}
...
}
Now, what I have tried are
getMockBuilder:
$mock = getMockBuilder(GuzzleWrapper::class)->onlyMethods(array('post'))->getMock();
$mock->expects($this->once())->method('post')->willReturn(response()->json([$responseData], 200));
Http::fake :
Http::fake(
[$uri => Http::response([$responseData], 200)
);
Mockery :
$mockGuzzleClient = Mockery::mock(GuzzleWrapper::class);
$mockGuzzleClient->shouldReceive('post')
->andReturn(response()->json([$responseData], 200));
I also tried Mockery like this:
$mockGuzzleClient = Mockery::mock(GuzzleWrapper::class, function (MockInterface $mock){
$mock->shouldReceive('post')
->andReturn(response()->json([$responseData], 200));
});
And like this:
$this->app->instance(
GuzzleWrapper::class,
Mockery::mock(GuzzleWrapper::class, function (MockInterface $mock){
$mock->shouldReceive('post')
->andReturn(response()->json([$responseData], 200));
})
);
and following everything I tried is the call to test my controller's function:
//Successfully add contacts to list
$this->json('POST', $addContactUri, $input_data, $token);
$this->seeStatusCode(201);
Now! Whatever I tried, it's as if the GuzzleWrapper is never mocked, it still does the post and doesn't return status code 200. No matter what I find on google, it never fits with this scenario... Can anyone help me?
Mocking is based on the container, for Laravel to pick up your mocked classes, you should never use the new keyword. Instead use the container by using resolve().
$client = resolve(GuzzleWrapper::class);
This should work with one of the following mock approach where you use Mockery::mock(). But the way you are mocking the response, without seing the GuzzleWrapper, i would not expect it to return a Laravel response or else it is custom code.
$mockGuzzleClient = Mockery::mock(GuzzleWrapper::class, function (MockInterface $mock){
$mock->shouldReceive('post')
->andReturn(response()->json([$responseData], 200));
});
The most correct way would to use the Http facade. Your call in the controller should look like this.
Http::post($uri, $data);
The mocking should look like this, in general it seems like you are combining guzzle and Http interchangeable in your mocking attempts and that wont fly. If you mock with Http use the Http facade.
Http::fake([
$uri => Http::response(['your data' => 'response'], 200, []),
]);
What if you convert the GuzzleWrapper to a dependency? What I mean is to declare the GuzzleWrapper as the private property of the Controller, this way, it would be easier to test the controller: you need to pass the proper GuzzleWrapper to the constructor.
class ContactController {
private GuzzleWrapper $client;
public function __construct(GuzzleWrapper $client)
{
$this->client = $client;
}
public function addContacts(Request $request)
{
...
$response = $this->client->post($uri, $data);
if($response->getStatusCode() != 200) {
return response()->json("Problem getting data", 500);
}
...
}
}
Once you have done this, it should be easier to test. Then, if you need to test the client, I would recommend either the PHPUnit Mocks or Mockery.
If you can avoid testing the client (maybe you already tested it properly in other places), then I would recommend looking at my composer package https://packagist.org/packages/doppiogancio/mocked-client.

barryvdh/laravel-dompdf renders blank web page

I'm upgrading from Laravel 7 to 8 and would like to switch to barryvdh/laravel-dompdf for PDF generation. I was using niklasravnsborg/laravel-pdf up until now, but since that package doesn't support Laravel 8, I need to switch. So I am in the processing of altering my existing code to use barryvdh/laravel-dompdf, but I'm running into an issue.
This is my (simplified) controller:
public function update(Request $request) {
$invoice = Invoice::find($request->invoice_id);
if(isset($request->export) AND $request->export == 1) {
$this->exportInvoice($invoice, $request);
}
}
This exportInvoice function is in the same controller file.
I'm using this to generate a test PDF:
$pdf = App::make('dompdf.wrapper');
$pdf->loadHTML('<h1>Test</h1>');
return $pdf->stream();
Now I've managed to narrow down the issue to the place in my code where the PDF generation fails.
If I put the PDF generation code in the if statement in the update function above, then I get the expected result: a simple PDF file.
However, as soon as I move this piece of code to the exportInvoice function, I get a simple blank web page.
I've been googling around, but I was unable to find similar issues.
I've tried putting all my code together in the update function and guess what ... This works as expected. It's as if I'm doing something wrong with the subfunctions, but I can't figure out what.
Does anybody see what I'm doing wrong?
From your update() method, this will stream a PDF back to the browser:
return $pdf->stream();
But from a exportInvoice(), called by the update() method, that will just return the stream back to the update() method. If you don't do anything with it there, it won't reach the browser. You need to return the response returned from exportInvoice().
public function update(Request $request) {
$invoice = Invoice::find($request->invoice_id);
if(isset($request->export) AND $request->export == 1) {
// Note we need to *return* the response to the browser
return $this->exportInvoice($invoice, $request);
}
}
public function exportInvoice($invoice, $request) {
$pdf = App::make('dompdf.wrapper');
$pdf->loadHTML('<h1>Test</h1>');
return $pdf->stream();
}

Laravel Nova - Observer Update Method Causes 502

When trying to update a resource in Laravel Nova that has a Observer the update loads for a while and then ends with a 502 error. The observer is registered correctly (the created method works fine) and I'm not trying to do anything special in the updated method. Any ideas?
public function updated(Model $model)
{
//
$model->title = 'test';
$model->save();
}
If I try this without the $model->save(), there is no 502 error but the change I want to happen also doesn't happen. I get the green success message, and any change I make on the form prior to updating occurs, but not the change I'm trying to make during the updated method.
Any help troubleshooting this would be appreciated
I am not very good at Laravel, but I think, that you should to try this:
In your model file add method:
public function saveQuietly(array $options = [])
{
return static::withoutEvents(function () use ($options) {
return $this->save($options);
});
}
Then, in your updated method in observer do something like this:
public function updated(Model $model)
{
$model->title = 'test';
$model->saveQuietly();
}

Laravel 5: Calling routes internally

Is there a way, in Laravel 5, to call routes internally/programmatically from within the application? I've found a lot of tutorials for Laravel 4, but I cannot find the information for version 5.
Using laravel 5.5, this method worked for me:
$req = Request::create('/my/url', 'POST', $params);
$res = app()->handle($req);
$responseBody = $res->getContent();
// or if you want the response to be json format
// $responseBody = json_decode($res->getContent(), true);
Source:
https://laracasts.com/discuss/channels/laravel/route-dispatch
*note: maybe you will have issue if the route you're trying to access
has authentication middleware and you're not providing the right credentials.
to avoid this, be sure to set the correct headers required so that the request is processed normally (eg Authorisation bearer ...).
UPDATE: i've tried this method with laravel 8 and it works but if you're using PHP version 8.0 you might need to call opcache_reset(); before this line $req = Request::create('/my/url', 'POST', $params); to avoid an error.
see guzzlehttp/guzzle dosn't work after update php to php 8 for more info
You may try something like this:
// GET Request
$request = Request::create('/some/url/1', 'GET');
$response = Route::dispatch($request);
// POST Request
$request = Request::create('/some/url/1', 'POST', Request::all());
$response = Route::dispatch($request);
You can actually call the controller that associates to that route instead of 'calling' the route internally.
For example:
Routes.php
Route::get('/getUser', 'UserController#getUser');
UserController.php
class UserController extends Controller {
public function getUser($id){
return \App\User::find($id);
};
}
Instead of calling /getUser route, you can actually call UserController#getUser instead.
$ctrl = new \App\Http\Controllers\UserController();
$ctrl->getUser(1);
This is the same as calling the route internally if that what you mean. Hope that helps
// this code based on laravel 5.8
// I tried to solve this using guzzle first . but i found guzzle cant help me while I
//am using same port. so below is the answer
// you may pass your params and other authentication related data while calling the
//end point
public function profile(){
// '/api/user/1' is my api end please put your one
//
$req = Request::create('/api/user/1', 'GET',[ // you may pass this without this array
'HTTP_Accept' => 'application/json',
'Content-type' => 'application/json'
]);
$res = app()->handle($req);
$responseBody = json_decode($res->getContent()); // convert to json object using
json_decode and used getcontent() for getting content from response
return response()->json(['msg' =>$responseBody ], 200); // return json data with
//status code 200
}
None of these answers worked for me: they would either not accept query parameters, or could not use the existing app() instance (needed for config & .env vars).
I want to call routes internally because I'm writing console commands to interface with my app's API.
Here's what I did that works well for me:
<?php // We're using Laravel 5.3 here.
namespace App\Console;
use App\MyModel;
use App\MyOtherModel;
use App\Http\Controllers\MyController;
use Illuminate\Console\Command;
class MyCommand extends Command
{
protected $signature = 'mycommand
{variable1} : First variable
{variable2} : Another variable';
public function handle()
{
// Set any required headers. I'm spoofing an AJAX request:
request()->headers->set('X-Requested-With', 'XMLHttpRequest');
// Set your query data for the route:
request()->merge([
'variable1' => $this->argument('variable1'),
'variable2' => $this->argument('variable2'),
]);
// Instantiate your controller and its dependencies:
$response = (new MyController)->put(new MyModel, new MyOtherModel);
// Do whatever you want with the response:
var_dump($response->getStatusCode()); // 200, 404, etc.
var_dump($response->getContent()); // Entire response body
// See what other fun stuff you can do!:
var_dump(get_class_methods($response));
}
}
Your Controller/Route will work exactly as if you had called it using curl. Have fun!

Laravel 4 testing session in controllers

I have a problem with testing controllers in Laravel 4.
I have the next code:
public function getRemind()
{
$status = \Session::get('status');
$error = \Session::get('error');
$email = \Session::get('email');
return \View::make('admin/reminds/remind_form', compact('status', 'error', 'email'));
}
And I want to test if correct data passed in views by controller:
public function testGetRemind()
{
\Session::set('status', 'status');
\Session::set('error', 'error');
\Session::set('email', 'email');
$response = $this->action('GET', 'Admin\RemindersController#getRemind');
$this->assertTrue($response->isOk(), 'Get remind action is not ok');
$this->assertViewHas('status', 'status');
$this->assertViewHas('error', 'error');
$this->assertViewHas('email', 'email');
}
But this doesn't work.
Also I can't mock Session-class, because it not allowed by framework - there are a lot of errors when I try doing it.
Call Session::start() at the start of your test. When you call an URL in a test, the session is started if it has not already been started, wiping any existing data put into it.
Since v4.1.23, you can also do $this->session($arrayOfSessionData), which handles the starting of the session for you.

Resources