I am encountering a very strange thing while doing testing with Laravel 4. It looks like a bug but there's probably a logical explanation.
I have replicated the "bug" in a clean Laravel install and here's my code:
My resource controller /app/controllers/TestController.php:
(Created with php artisan controller:make TestController)
class TestController extends \BaseController {
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
return Response::json(array());
}
// The end of the file is unchanged
In my app/routes.php:
Route::get('/', function()
{
return View::make('hello');
});
// Note the composed part of the URL.
// My problem isn't present if I just use `myapi` or `path` as a url
Route::resource('myapi/path', 'TestController');
Added in /app/test/ExampleTest.php:
public function testTest()
{
$res = $this->call('GET', 'myapi/path');
// Until here everything works right
$this->assertEquals(json_decode($res->getContent()), array());
// Now I call the same URL a second time
$res = $this->call('GET', 'myapi/path');
$this->assertEquals(json_decode($res->getContent()), array());
}
Now I run phpunit and here's what I get:
There was 1 error:
1) ExampleTest::testTest
Symfony\Component\HttpKernel\Exception\NotFoundHttpException:
/home/me/Web/laraveltest/bootstrap/compiled.php:5531
/home/me/Web/laraveltest/bootstrap/compiled.php:4848
/home/me/Web/laraveltest/bootstrap/compiled.php:4836
/home/me/Web/laraveltest/bootstrap/compiled.php:4828
/home/me/Web/laraveltest/bootstrap/compiled.php:721
/home/me/Web/laraveltest/bootstrap/compiled.php:702
/home/me/Web/laraveltest/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Client.php:81
/home/me/Web/laraveltest/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php:332
/home/me/Web/laraveltest/vendor/laravel/framework/src/Illuminate/Foundation/Testing/ApplicationTrait.php:51
/home/me/Web/laraveltest/app/tests/ExampleTest.php:25
In my other project I get a slightly different backtrace, but I have the impression that's the same problem: (but I have no idea of why the other is compiled and this one not)
2) UnitModelTest::testOther
Symfony\Component\HttpKernel\Exception\NotFoundHttpException:
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php:148
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/Router.php:1049
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/Router.php:1017
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/Router.php:996
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:775
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:745
/home/me/Web/my-project/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Client.php:81
/home/me/Web/my-project/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php:327
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Testing/ApplicationTrait.php:51
/home/me/Web/my-project/app/tests/UnitModelTest.php:32
In both case the line given in the trace for the test file corresponds to the second call of the test.
As I noted in the comments of the routes.php file, if I use a simple url with no slash, the test passes without problem.
I have no problem when I use the api from the browser.
I found many topics related to the NotFoundHttpException on StackOverflow, but none looks like mine. This one is specifically present when testing and only trigger an error at the second call.
So what am I doing wrong here ? Or is it really a bug ?
The problem is that calls made with the same client will use the provided URI relatively. That means what you actually call is:
myapi/path
myapi/myapi/path
You can fix this if you add a preface the urls with a / to make them absolute to the root of the application.
public function testTest()
{
$res = $this->call('GET', '/myapi/path');
// Until here everything works right
$this->assertEquals(json_decode($res->getContent()), array());
// Now I call the same URL a second time
$res = $this->call('GET', '/myapi/path');
$this->assertEquals(json_decode($res->getContent()), array());
}
If you experience other issues like that it often helps to call
$this->refreshApplication();
(This would also create a new client and therefore solve this issue as well)
In my case this error happened because i change public directory name to public_html my solution was put this in the \App\Providers\AppServiceProvider register method.
public function register()
{
// ...
$this->app->bind('path.public', function() {
return base_path('public_html');
});
}
Related
I have a controller has an action that looks something like this:
/**
* #Route("/my_route_path", name="my_route_name")
*/
public function doSomethingAction(Request $request)
{
$myPath = $request->getScheme().'://'.$request->getHttpHost().''.$request->getBasePath();
$data = file_get_contents($myPath. '/data_folder/data.json');
return $this->render('#Entry/my_template.html.twig', array(
'data' => json_decode($data, true)
));
}
And I create a functional test for this controller like this:
/** #test */
public function doSomething_should_success()
{
$client = static::createClient();
$crawler = $client->request('GET', '/my_route_path');
$this->assertEquals(200, $client->getResponse()->getStatusCode());
}
But I can't run the functional test I still get : Failed asserting that 500 is identical to 200
So, after I checked the test.log file I find this error : file_get_contents(http://localhost/data_folder/data.json) : failed to open stream
As now the problem is comming from $request->getBasePath() because always contain empty string but the expected behaviour is return PATH_TO_MY_PROJECT_FOLDER\web in my case must return projects\web_apps\MY_PROJECT_FOLDER_NAME\web
So, the simplified question: why the request object always contain an empty basePath string in the unit test but it works very well on the browser.
The Request object helps you handle the request of a client, that is something like GET /my_route_path plus lots of headers and a server that is directed at.
The web server passes those information on to php and symfony, and symfony will turn this into a Request object. Symfony has usually one entry point, which is public/index.php (symfony 4) or web/app.php (symfony 3) which is assumed to be / or possibly /basePath/ (the basepath will be communicated by the web server and handled by Symfony).
Symfony will generate a Request object, where the basepath is essentially abstracted away, and whenever you generate a url (via Controller::generateUrl) the base path is taken into account. that's why the basepath is important for Requests.
This is actually described pretty well in the comments of the Request's functions:
getBasePath vs getPathInfo.
However, this only concerns the public facing URLs and doesn't have anything to do with how you structure your project and where that project is located, because that's completely irrelevant to the Request (separation of concerns and stuff).
So I guess, you are actually looking for the root directory of your project.
To find the location of your project dir, there is the very base version, where you directly use the PHP magic var __DIR__ which contains the directory the current script file is in, and you can navigate from there. since controllers are usually located such that their path is projectdir/src/Controller/TheController.php a __DIR__.'/../.. would give you the projectdir. However, that's not really clean. The better version:
Depending on the symfony version you're using, you should retrieve the project dir via the ParameterBagInterface (symfony 4)
function doSomethingAction(ParameterBagInterface $params) {
$projectDir = $params->get('kernel.project_dir');
}
or via the container (symfony 3) see also: new in symfony 3.3: A simpler way to get the project root directory
function doSomethingAction() {
$projectDir = $this->getParameter('kernel.project_dir');
}
In my case I had to inyect RequestStack $stackand access the main request, after that my "BasePath" has value. This is because I where in a subrequest and I had to access to the top level of the request.
This post helped me to understood: Symfony2 - get main request's current route in twig partial/subrequest
/**
* #Route("/myroute", name="myroute")
*/
public function myroute(RequestStack $stack)
{
$request = $stack->getMainRequest();
$route = $request->getPathInfo();
}
I'm having odd behaviour in a laravel 5.5 project. I have some feature tests set up and working, and need to test that a specific route will return a 404 if the id passed in doesn't exist. I have explicit model binding set up in the RouteServiceProvider for my Note model
Route::bind('note', function($value){
return Note::where('id', $value)->first() ?? abort(404);
});
This works for my get route test. This test below passes as expected. ($this->headers is just some bits I set in a setUp method that is needed for a lot of the tests)
/** #test */
public function error_received_if_note_does_not_exist()
{
$this->withExceptionHandling();
$response = $this->json('GET', '/api/v1/note/1', [], $this->headers);
$response->assertStatus(404);
}
but this one for the delete route fails ...
/**
* #test
* #expectedException \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function error_received_if_note_not_found()
{
$this->withExceptionHandling();
$response = $this->json('DELETE', '/api/v1/note/1', [], $this->headers);
$response->assertStatus(404);
}
with message
Failed asserting that exception of type "\Illuminate\Database\Eloquent\ModelNotFoundException" is thrown.
I get that technically the exception is correct, but I want to assert that I get a 404 error code.
Here is the routes/api.php file
Route::apiResource('note', 'NoteController')->only([
'show',
'destroy'
]);
I'm pulling my hair out. Any ideas welcome.
I think Ive figured this out encase anyone else has this problem.
In the delete test, I removed the $this->withExceptionHandling(); line. Which then allows the test to pass.
If I do the same in the get test it fails. So the get test needs it, but the delete test doesn't.
Randomly.
This laravel route is returning the image sometimes and is returning an error page the other times. What could go wrong.
This route just returns the image like this:
public function image(Request $request) {
return response()->file(storage_path('app/'.$request->input('path')));
}
I'm storing images using this code:
$path = $request->p->store('public/images');
Storage::setVisibility($path, 'public');
I don't know what's going wrong. I see this log when the route is requested.
NOTICE: PHP message: [2017-09-25 18:33:25] production.ERROR:
Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException:
The file
"/app/storage/app/public/images/YxNllK0iVKAxxUSCEhFHLo5sGZiXg5NNbGDbOVSL.jpeg"
does not exist in /app/vendor/symfony/http-foundation/File/File.php:37
Here's the route in it's full glory:
https://resonant-tower-177816.appspot.com/showimage?path=public/images/YxNllK0iVKAxxUSCEhFHLo5sGZiXg5NNbGDbOVSL.jpeg
This is definitely not because there's no image there. I can assure you the route works some of the times.
What could have gone wrong?
I'm using Laravel 5.4 on Google App Engine.
Wrong path "/app/storage/app/public/images"
The correct path is like "/storage/app/public/images"
So you should change
public function image(Request $request) {
return response()->file(storage_path('app/'.$request->input('path')));
}
to
public function image(Request $request) {
return response()->file(storage_path($request->input('path')));
}
<?php
include(APPPATH.'/libraries/REST_Controller.php');
class Quiz extends REST_Controller{
function __construct()
{
// Call the Model constructor
parent::__construct();
}
public function user_get()
{
$this->load->model('Quizmodel');
$data = $this->Quizmodel->getAll();
$this->response($data, 200);
}
function restclient()
{
$this->load->library('rest', array(
'server' => 'http://localhost/CodeIg/index.php/quiz/'
));
$userr = $this->rest->get('user','','json');
echo $userr;
}
}
?>
I am able to get JSON output if I type http://localhost/CodeIg/index.php/quiz/user in my browser, however if I type http://localhost/CodeIg/index.php/quiz/restclient it gives this error: {"status":false,"error":"Unknown method"}
I tried changing get to post but still the same error.
I referred this page https://code.tutsplus.com/tutorials/working-with-restful-services-in-codeigniter--net-8814 to do it.
You pinged me on GitHub, even though I haven't used or even thought about this code in at least 4 years.
https://github.com/chriskacerguis/codeigniter-restserver/blob/d19dc77f03521c7a725a4555407e1e4e7a85f6e1/application/libraries/REST_Controller.php#L680
This is where that error is being triggered. Throw a few breakpoints in there or var_dump()'s until you see what is causing the trouble.
You probably want to get off CodeIgniter though, and use something more actively maintained like SlimPHP or Lumen.
firstly I want as you have loaded rest api and created your controller quiz as an api to call , where you can only create your functions like user_get or restclient_get and access them the same manner you are doing.Just change you function name restclient to restclient_get then it will call instead it is even not running at this moment.
I defined a test which tests the creation of a user. The controller is set to redirect back to the same page on error (using validation through a generated App\Http\Requests\Request). This works correctly when manually clicking in a browser, but fails during a test. Instead of being redirected to:
http://localhost/account/create
The test redirects to (missing a slash):
http://localhostaccount/create
Neither of these urls are what I have setup in the .htaccess or in the $url variable in config/app.php. Which is (On OSX Yosemite):
http://~username/laravel_projects/projectname/public
I finally pinpointed the issue to have something to do with how the result of Request::root() is generated. Making a call to this outside of a test results in the expected value defined in .htaccess and $url. Inside the test it results in:
http://localhost
What configuration needs to change in order to get this function to return the correct value in both contexts?
I should also mention I made the painful upgrade from Laravel 4 to the current version 5.0.27.
****** UPDATE *******
I was able to figure out an acceptable solution/workaround to this issue!
In Laravel 5, FormRequests were introduced to help move validation logic out of controllers. Once a request is mapped to the controller, if a FormRequest (or just Request) is specified, this is executed before hitting the controller action.
This FormRequest by default handles the response if the validation fails. It attempts to construct a redirect based on the route you posted the form data to. In my case, possibly related to an error of mine updating from Laravel 4 to 5, this default redirect was being constructed incorrectly. The Laravel System code for handling the response looks like this:
/**
* Get the proper failed validation response for the request.
*
* #param array $errors
* #return \Symfony\Component\HttpFoundation\Response
*/
public function response(array $errors)
{
if ($this->ajax() || $this->wantsJson())
{
return new JsonResponse($errors, 422);
}
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors, $this->errorBag);
}
Notice how the returned redirect is NOT the same as calling Redirect::route('some_route'). You can override this response function by including use Response in your Request class.
After using Redirect::route() to create the redirect, the logic in my tests passed with the expected results. Here is my Request code that worked:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Http\Requests\Request;
use Response;
class AccountRequest extends FormRequest {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'email' => 'required|max:50|email|unique:users',
'password' => 'required|min:6',
'password_confirmation' => 'required|same:password'
];
}
public function response(array $errors){
return \Redirect::route('account_create');
}
}
The important part is that I called Redirect::route instead of letting the default response code execute.
Override the response function in the FormRequest validation handler to force the redirect to be constructed with Redirect::route('named_route') instead of allowing the default redirect.
You need to change config/app.php file's url value. Default value is http://localhost
Doc from config/app.php
This URL is used by the console to properly generate URLs when using the Artisan command line tool. You should set this to the root of your application so that it is used when running Artisan tasks.
I know this isn't an exact answer to your question since it is not a configuration update that solves the problem. But I was struggling with a related problem and this seems to be the only post on the internet of someone dealing with something similar - I thought I'd put in my two cents for anyone that wants a different fix.
Please note that I'm using Laravel 4.2 at the moment, so this might have changed in Laravel 5 (although I doubt it).
You can specify the HTTP_HOST header when you're testing a controller using the function:
$response = $this->call($method, $uri, $parameters, $files, $server, $content);
To specify the header just provided the $server variable as an array like so:
array('HTTP_HOST' => 'testing.mydomain.com');
When I did the above, the value produced for my Request::root() was http://testing.mydomain.com.
Again, I know this isn't a configuration update to solve you're issue, but hopefully this can help someone struggling with a semi-related issue.
If you tried changine config/app.php and it did not help.
it is better to use $_ENV - global variable in phpunit.
say, you want Request::root() to return 'my.site'
but you cannot touch phpunit.xml
you can simply set an env param like so
$_ENV['APP_URL'] = 'my.site';
and call $this->refreshApplication(); in your unittest.
viola, your request()->root() is giving you my.site now.