How to do simple testing of a Laravel controller? - php

In most web apps I've worked on, I can create a simple test script (test.php), load all dependencies (usually through an autoloader), setup the database connection, make calls to any class methods, then examine the results they return to make sure everything looks right.
Example:
$test = new Item;
var_dump($test->getItemStatus($itemid));
The above would show a nice output of the values it returns for that itemid.
With Laravel it appears to be much more complex to just perform this simple test...or maybe I am overcomplicating it and there is a simple way to do this.
Suppose I want to do the same thing in a Laravel 4 app. I want to test the output of method getItemStatus in controller named ItemsController that uses the model Items.
If I try the following, I get an Symfony \ Component \ Debug \ Exception \ FatalErrorException error:
$items = App::make('ItemsController')->getItemStatus($itemid);
If I define this route in routes.php:
Route::get('items/get-item-status', array('as' => 'getItemStatus', 'uses' => 'ItemsController#getItemStatus'));
Then try the following, I get Symfony\Component\HttpKernel\Exception\NotFoundHttpException in the request output:
$request = Request::create('items/get-item-status', 'GET', array());
$items = Route::dispatch($request)->getContent();

In my opinion in Laravel is much more simple and maybe that's why you might be overthinking it.
Let's start with a very basic router. Here are some options to do simple debug using Laravel applications in a basic router/controller:
Route::get('test', function() {
$test = new Item;
dd($test);
Log::info($test); // will show in your log
var_dump($test);
});
Now follow:
http://yourserver.com/test
And it should stop on
dd($test);
Vardumping and dying at that line. Comment it, try again, and so on.
All of these will also work in a controller:
Route::get('test/{var?}', 'TestController#index');
The very same way:
class TestController extends Controller {
public function index($var = null)
{
$test = new Item;
dd($var);
dd($test);
Log::info($var);
Log::info($test);
var_dump($test);
var_dump($var);
}
}
Your route:
Route::get('items/get-item-status', array('as' => 'getItemStatus', 'uses' => 'ItemsController#getItemStatus'));
Should also work, if you follow:
http://yourserver.com/items/get-item-status
If it doesn't, try to follow
http://yourserver.com/public/index.php/items/get-item-status
Or just
http://yourserver.com/public/items/get-item-status
Because you might have a virtual host or .htaccess configuration problem.

Related

Laravel 5 / Codeception not routing correctly

I'm trying to write an API test case for a controller function using codeception, and I'm hitting an issue where the route to the controller function does not appear to be evaluated correctly, and the evaluation seems to be different depending on what I have in my test case.
Here is a code sample from my test case:
use \ApiTester;
class CustomerRegisterCest
{
// tests
public function testGetRegister(ApiTester $I)
{
$I->sendGET('register');
$I->seeResponseCodeIs(200);
}
public function testPostRegister(ApiTester $I)
{
$I->sendPOST('register', [
// set the data in here
]);
$I->seeResponseCodeIs(200);
}
I have a routes.php file containing these routes:
Route::get('/', ['as' => 'home', 'uses' => 'HomeController#getIndex']);
Route::get('register', ['as' => 'getRegister', 'uses' =>'RegistrationController#getRegister']);
Route::post('register', ['as' => 'postRegister', 'uses' => 'RegistrationController#postRegister']);
I have inserted some debug statements into my controller classes so that I can see what routes get run, like this:
Log::debug('GET register'); // or GET index or POST register, etc
At the moment I have stripped down everything from my controller classes so that ONLY the debug statements are included.
When I run the test case as above, I get the following debug output:
GET register
GET index
... so it appears that sendPOST('register', ...) actually routes to the GET route for "/" instead of the POST route for "/register". Outside of the test case everything works normally -- I can POST to the register routes fine, routing appears to work OK, the problem only appears inside a codeception test case.
If I change the test case so that I am doing the sendGET and the sendPOST inside the same function call, for example like this:
// tests
public function testPostRegister(ApiTester $I)
{
$I->sendGET('register');
$I->seeResponseCodeIs(200);
$I->sendPOST('register', [
// set the data in here
]);
$I->seeResponseCodeIs(200);
}
then I see this debug output:
GET register
GET register
... so that by inserting the sendGET into the same function as the sendPOST, it has changed the sendPOST behaviour so that it now routes to the GET route for register instead of the GET route for index (but still won't route to the correct POST route).
I have tried turning xdebug on and don't have any clues from the xdebug output as to what's going on either.
I think I found the answer after a lot of command line debugging (using phpstorm):
The POST register route handling function in the controller was declared like this:
public function postRegister(RegistrationRequest $request)
{
... requiring an instance of Request to be passed in via dependency injection. That request contained some validation code and if for some reason the validation code could not complete (e.g. throws an exception) then the controller function never gets called -- because building the request fails.
This, in browser-land, throws a 500 error but in codeception land the exception is trapped differently and it returns a redirect to / with no data. This all happens outside of the controller function rather than inside it, so that the Log statement in the controller function never runs because the function never gets called. The exception handler in codeception is a generic trap.
The implicit suggestion is that maybe dependency injections in controllers are a bad idea. Or, maybe, that generic exception handlers are a bad idea.

Laravel NotFoundHttpException (RouteCollection->match)

I am new to Laravel and was following a tutorial (https://www.youtube.com/watch?v=Zz_R73eW3OU&list=PL09BB956FCFB5C5FD). I have just installed the latest version of Laravel and am going through the beginning stages of the tutorial. So far (installation, creating a migration) I was doing ok, but now I am stuck at the point of creating new routes.
I have created a controller in app/controllers:
<?php
class Slots_Controller extends Base_Controller{
public $restful = true;
public function get_index(){
return View::make( 'slots.index' );
}
}
As well as a very minimalist view in the file app/views/slots/index.php
<h1>Slots</h1>
My app/routes file looks like this :
Route::get('/', function()
{
return View::make('hello');
});
Route::get( '/public/slots', array(
'uses' => 'slots#index'
) );
I've searched a number of things on the web and here is how far I have come :
- i know ideally only the public dir should be in the htdocs, i will be doing that presumably at a further stage of the tutorial. so as of now, my laravel home page is at http://localhost:8888/my_directory.laravel/public/
- i have checked the apache mod-rewrite : it is on and it works well for codeIgniter
- i have checked that the .htaccess is as provided by laravel (and as suggested by different posts)
The weird thing is, if i change the routes for the home page like this :
Route::get('/', function()
{
return 'hello world';
// return View::make('hello');
});
Nothing changes, i.e. i still get the original welcome page. My understanding is this modification should have simply output "hello world" as the html for the home page.
So it seems that whatever I'm doing in routes.php has no effect whatsoever, as if I was modifying the wrong file or something.
I've spent the most part of an otherwise sunny afternoon on this, any help is much apreciated :)
Thanks,
Tepp
There are many things you are doing wrong here (The best way is to follow the tutorial on Laravel):
In your controller file you are mentioning "Base_Controller", are you sure you have such a file in your controllers directory? (According to my knowledge it should be BaseController)
The Route::get is not defined properly. There are many ways to define a route; however for your case it should be something like this:
Route::get('slots', array(
'uses' => 'Slots_Controller#get_index'
));
Hope this helps.

Route::get('(:any)',function() not working properly in laravel 4

The program is a URL shortener that I am just trying out from some tutorial ( for Laravel 3 but i am using laravel 4) and it is supposed to give me the "some URL" as output when I click on the shortened URL.
<?php
Route::get('/', function()
{
return View::make ('home.index');
});
Route::post('/',function(){
$url = Input::get('url');
// If the url is already in the table, return it
$record=Url::whereurl($url)->first();
if ($record) {
//then return it
return View::make('home.result')
->with('shortened', $record->shortened);
}
});
Route::any('{shortened}',function($shortened)
{ echo "Everything is ok with: ".$shortened; })
->where('shortened', '.*');
Rather it is going to a error page saying
"Not Found The requested URL /asdf was not found on this server."
I think it is a very simple error on my part. I am not sure whether they have changed the syntax or the keyword. I am not able to find any solution from the laravel docs as well.
I use the following for my CMS to catch all requests with a Regex filter:
Route::get('{uri}', 'PublicController#init')
->where('uri', '.*');
In your case it would probably look like this:
Route::get('{uri}', function($uri) {
return 'Hello, world!';
})->where('uri', '.*');
You can use the first one to load a method inside of a controller. The controller being PublicController and the method init. There you can handle your errors initiate any other processes based on the request.
This works really well, you can add reserved routes before ths route so it becomes the last route catching all requests that are not catched by previous routes.
In Laravel 4, you do:
Route::get('{shortened}', function($shortened)
instead of
Route::get('(:any)',function($shortened)
You can read more about route parameters in Laravel 4, here: http://laravel.com/docs/routing#route-parameters
Lauch Laravel
go to you'r application root,run the command PHP artisan serve
Put the routing
-Comment evrything in routing.php, add these routes:
Route::get('/', function()
{
echo 'hello';
});
Route::any('{shortened}',function($shortened){
echo "Evrything is ok with: ".$shortened;
})->where('shortened', '.*');
Launch you application
-go to http://localhost:8000/asdf

Unable to run multiple controller tests in Laravel

I'm attempting to clean up an existing application by writing unit tests for some legacy code (and updating it along the way). I've rewritten a number of libraries and I've really been loving the TDD approach. However, now it's time to move on to testing some controllers, and I've run into a problem on the very first set of tests. I'm following the explanations in Jeffery Way's Laravel Testing Decoded.
The goal here is to test my login route: http://my-development-server/login. The code should work like this: first, check to see if someone is already logged in - if they are, redirect them to the dashboard (the main page in the app). Otherwise, render the login page. Pretty straight forward.
Here are the routes involved:
Route::get('login', array(
'as' => 'login',
'uses' => 'MyApp\Controllers\AccountController#getLogin',
));
Route::get('/', array(
'as' => 'dashboard',
'uses' => 'MyApp\Controllers\DashboardController#showDashboard',
'before' => 'acl:dashboard.view',
));
Here's the AccountController::getLogin method:
public function getLogin()
{
// Are we logged in?
if (\Sentry::check())
return Redirect::route('dashboard');
// Show the page.
return View::make('account.login');
}
I'm using the Sentry library for user authentication.
And here's my first test:
class AccountControllerTest extends TestCase {
public function tearDown()
{
Mockery::close();
}
public function test_login_alreadyLoggedIn()
{
// arrange
//
\Sentry::shouldReceive("check")
->once()
->andReturn(true);
// act
//
$response = $this->call("GET", "/login");
// assert
//
$this->assertRedirectedToRoute("dashboard");
}
}
This test emulates the "user attempts to log in when they're already logged in" case. check returns true (already logged in) and the user is redirected to the route named dashboard. It works perfectly, test passes.
Next, I add a new test, to test the "user attempts to log in when nobody's logged in" case. Here's the code for that test:
public function test_login_notLoggedIn()
{
// arrange
//
\Sentry::shouldReceive("getUser")
->twice()
->andReturn(null);
\Sentry::shouldReceive("check")
->once()
->andReturn(false);
// act
//
$response = $this->client->request("GET", "/login");
// assert
//
$h2 = $response->filter("h2");
$this->assertEquals("Please sign in", $h2->text());
}
When I run this test, the first test passes but I get a NotFoundHttpException in the second test method.
There are a couple of strange things about this problem:
if I comment out the first test method (test_login_alreadyLoggedIn), the second test passes.
if I reverse the order of the two test methods, test_login_notLoggedIn passes and test_login_alreadyLoggedIn fails (ie. the first method in the class passes and the second fails).
This looks to me like some sort of configuration issue - after the first test (which passes), something is messed up and it cannot make the request in the second test - but I'm at a loss. I've gone over the book several times and google but can't find any reference to any configuration that I'm missing.
Any suggestions?
For anyone who may run into this problem in the future...
This was caused by problems with my routes.php file. In particular, I organized by routes into a set of files:
accounts.php - contains routes related to "account" functions
admin.php - contains routes related to "admin" functions
etc...
These files were then included in my routes.php file with require_once. This works fine for my application, but for some reason, the routes were not loaded properly during testing. The solution is to move my routes back to the routes.php file until I can find a better way to organize them.
You can solve this problem by replacing your require_once() to require()

Silex (Symfony2 micro framework) redirect in bootstrap.php

I would like to create a redirect when the user session expires. I know how to do this like so:
$app->before(function (Request $request) {
if (!$request->getSession()->get('displayName')) {
return new RedirectResponse('login', 301);
}
});
The problem is that I get the error : Fatal error: Class 'RedirectResponse' not found in /Applications/MAMP/htdocs/pst/app/bootstrap.php on line 120
I also tried to add this in the if statement:
$app->get('/', function () use ($app) {
return $app->redirect('/hello');
});
but the app variable isn't defined. What is the right way to do this?
UPDATE:
I added the RedirectResponse function from Symfony2. But the displayName isn't correct. I dumped by session, could anyone take a look of what it should be?
DUMP: http://pastebin.com/7XQbJJ08
In my layout.twig (general layout) I have <span> {{ app.user.displayName }}</span> and after a while no reaction he says he can't find the attribute displayName on a null attribute.
For the first approach you'll need to make sure you've imported the RedirectResponse first. You can do this by adding the following to the top of your script:
use \Symfony\Component\HttpFoundation\RedirectResponse;
If this still doesn't work then you may have autoloader problems.
For the second case I'm not sure how $app could be undedfined. As this will have been created when you called:
$app = new Silex\Application();

Categories