Laravel 5: Controller Testing - PHPUnit returns green - php

the following simple controller test makes a 'GET' request to the PostsController#index action:
<?php
class PostsControllerTest extends TestCase {
public function testIndex()
{
$response = $this->action('GET', 'PostsController#index');
}
}
In my understanding, if the index method does not exist in my controller, I shouldn't get the green light, when calling phpunit in my command line.
Yet my controller looks like this:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* Display a listing of the resource.
*
* #return Response
*/
// public function index()
// {
// //
// return 'Posts Index';
//}
}
As you can clearly see the index method is commented out and I still get this:
**OK (1 test, 0 assertions)**
Any suggestions?

You aren't making any assertions. Your test isn't checking if $response is "OK".
Change your test to this:
public function testIndex()
{
$response = $this->action('GET', 'PostsController#index');
$this->assertEquals(200, $response->status());
}
This test asserts that the page responded with a 200 status code, which means it was successful.
You can read up on Laravel's testing here.

Related

How to properly call laravel's middleware closure in phpunit?

How to properly call this middleware closure function in phpunit test so that $user will be set?
Auth::user is mocked and will receive the proper user object, but the function is not called when creating the controller instance.
Here is my code:
use App\Http\Controllers\Controller;
class CustomController extends Controller
{
private $user;
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
return $next($request);
});
}
}
Phpunit:
use Illuminate\Foundation\Testing\TestCase;
use Illuminate\Support\Facades\Auth;
use App\Models\User;
use App\Controllers\CustomController;
class CustomControllerTest extends TestCase
{
use CreatesApplication;
private $customController;
public function setUp()
{
Auth::shouldReceive('user')->andReturn(new User());
$this->customController = $this->app->make(CustomController::class);
}
}
You are resolving the controller out of the container, but then you aren't passing it any requests to handle. Your middleware code will only be executed when the controller is given something to handle.
As such, $this->middleware(...) simply says "execute this middleware when a request is sent through the pipeline".
Simply test a standard request hitting your endpoint, and then assert what you need to assert.
$this->get('your_endpoint');

Passing class name to view policy on controller index method returns error that too few arguments were passed

I'm trying to test a policy for the index method of my controller. I'm passing the class name into the authorize helper, but I'm receiving a 500 status and an error that says
1)
Tests\Feature\FunderFeatureTest::an_admin_should_be_able_to_view_a_list_of_funders
Symfony\Component\Debug\Exception\FatalThrowableError: Too few
arguments to function App\Policies\FunderPolicy::view(), 1 passed in
/home/vagrant/code/rtl/vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php
on line 614 and exactly 2 expected
What am I doing wrong? All other policies work (including the create() and store() methods which do not require a model). I realize this is similar to many other questions on SO, however the majority seem to be due to the fact that people weren't passing the class name to the authorize method or the problem occurs on a different controller method (such as update()). My apologies if this specific question was asked before, I've been researching this off an on for two weeks and can't quite find the answer I'm looking for.
FundersController.php
namespace App\Http\Controllers;
use App\Funder;
use Illuminate\Http\Request;
class FundersController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
$this->authorize('view', Funder::class);
return view('funders.index', [
'funders' => Funder::all(),
]);
}
}
FunderPolicy.php
namespace App\Policies;
use App\User;
use App\Funder;
use Illuminate\Auth\Access\HandlesAuthorization;
class FunderPolicy
{
use HandlesAuthorization;
public function view(User $user, Funder $funder)
{
return ($user->isAdmin() || $user->isSuperAdmin());
}
}
FunderFeatureTest.php (For reference)
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class FunderFeatureTest extends TestCase
{
use RefreshDatabase;
/** #test */
public function an_admin_should_be_able_to_view_a_list_of_funders()
{
$this->withoutExceptionHandling();
$this->signIn('admin');
$this->get('/funders')
->assertOk();
}
/** #test */
public function a_user_should_not_be_able_to_view_a_list_of_funders()
{
$this->signIn();
$this->get('/funders')
->assertStatus(403);
}
}
I'm not sure if this is the appropriate way to get this to work, but passing a new instance of a Funder model instead of the class name seemed to do the trick.
Changing $this->authorize('view', Funder::class); to $this->authorize('view', new \App\Funder());

Functional testing a controller called via twig render method

I have the following controller:
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class SidebarController extends Controller
{
public function displayCurrentUserInfoToTheSideBarAction()
{
$auth_checker = $this->get('security.authorization_checker');
$token = $this->get('security.token_storage')->getToken();
$user = $token->getUser();
return $this->render('widgets/sidebar_profile.html.twig',['user'=>$user]);
}
}
And I want somehow to test it but I am stuck because I invoke this method via twig's render function:
{{ render(controller('AppBundle:Sidebar:displayCurrentUserInfoToTheSideBar')) }}
What actually I want t ensure is that this particular controller renders the correct html. Usual functional tests on controllers in symphony is by emulating a homepage visit and checking any http related such as headers, response code and parsing the html in order to see if the site's behavior is as it should be, for example:
namespace Tests\AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class DefaultControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request('GET', '/');
$response=$client->getResponse();
$this->assertEquals(302, $response->getStatusCode());
$this->assertEquals('/login',$response->headers->get('Location'));
}
}
This test checks whether a visit to the homepage redirects to the login screen. But on the controller above how I can perform similar tests?

Yii2: Web service Request and Response Logger

I am trying to create a Request and Response Logger for a web service created in Yii 2.0.5 for debugging purpose.
Basically my motive is to track all the request, request data, response data and for this purpose I am using Yii Events. So far I have coded something like this:
UserController
use yii\rest\ActiveController;
use yii\base\Event;
use yii\web\Response;
Event::on(ActiveController::className(), ActiveController::EVENT_AFTER_ACTION, ['app\models\LogHandler', 'saveRequest'], ['request' => Yii::$app->request->getRawBody(), 'response' => Yii::$app->response->content]);
class UserController extends ActiveController
{
public $modelClass = 'app\models\User';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['contentNegotiator']['formats']['text/html'] = Response::FORMAT_XML;
return $behaviors;
}
// POST demo
public function actionDemo()
{
$data = array('status' => 200, 'message' => 'Success');
return $data;
}
}
In the above code if you have noticed then I have used the Class level Event handler (doc). Here I am trying to capture the Controllers EVENT_AFTER_ACTION event and passing the Request & Response object to my LogHandler's static method. However in my handler I am able to get the request's raw body but I am not able to get the response data which the actionDemo() is returning.
LogHandler
namespace app\models;
use yii\db\ActiveRecord;
class LogHandler extends ActiveRecord
{
public static function tableName()
{
return 'request_log';
}
public static function saveRequest($event)
{
// self::load($event);
// self::save();
var_dump($event);
}
}
How do I get the response data as well...
According to the Yii guide here, the best way to create a logger component is to override the class yii\log\Target. Then to send the logs, you need to override the abstract method export() of this class
Since this class is accessed after the life cycle of the request, you will have the request and response data Yii::$app->request and Yii::$app->response and you can access them to create whatever message you want. This is an example of the LogHandler class (this can be edited to include whatever details you want about the request and response)
namespace app\models;
use yii\db\ActiveRecord;
class LogHandler extends ActiveRecord {
$requestPath;
$responseBody;
public static function tableName() {
return 'request_log';
}
}
And an example of a logger class is as follows:
namespace app\components\Logs;
use yii\helpers\VarDumper;
use yii\log\Target;
use Yii;
class LoggerComponent extends Target {
public function export() {
$logMessage = new LogHandler();
$logMessage->requestPath = VarDumper::export(Yii::$app->request->absoluteUrl);
$logMessage->responseBody = VarDumper::export(Yii::$app->response->data);
$logMessage->save();
}
}

Extend Request class in Laravel 5

I'm new to Laravel (only experienced Laravel 5, so no legacy hang up here)
I'd like to know how to extend the core Request class. In addition to how to extend it, i'd like to know if it's a wise design decision to do so.
I've read through the documentation extensively (especially with regards to registering service providers and the manner in which it provides Facades access to entries within the dependency container) - but I can see (and find) no way to replace the \Illuminate\Http\Request instance with my own
Here is Official Document: Request Lifecycle
Content of app/Http/CustomRequest.php
<?php namespace App\Http;
use Illuminate\Http\Request as BaseRequest;
class CustomRequest extends BaseRequest {
// coding
}
add this line to public/index.php
$app->alias('request', 'App\Http\CustomRequest');
after
app = require_once __DIR__.'/../bootstrap/app.php';
change the code at public/index.php
Illuminate\Http\Request::capture()
to
App\Http\CustomRequest::capture()
I was working on the same issue today and I think it's worth mention that you may just change
Illuminate\Http\Request::capture()
to
App\Http\CustomRequest::capture()
without adding line
$app->alias('request', 'App\Http\CustomRequest');
because inside capture() method laravel actually binds provided class to service container with 'request' as a key
I guess you will have to extend also RequestForm. I use trait to avoid code duplication. Code below is relevant for Laravel 5.3.
app/Http/ExtendRequestTrait.php
<?php
namespace App\Http\ExtendRequestTrait;
trait ExtendRequestTrait {
methodFoo(){}
methodBar(){}
}
app/Http/Request.php
<?php
namespace App\Http;
use Illuminate\Http\Request as BaseRequest;
class Request extend BasicRequest {
use ExtendRequestTrait;
}
app/Http/FormRequest.php
<?php
namespace App\Http;
use Illuminate\Foundation\Http\FormRequest as BaseFormRequest;
class FormRequest extend BasicFormRequest {
use ExtendRequestTrait;
}
For phpunit test working you will have to override call method to make it using right Request class here Request::create.
test/TestCase.php
<?php
use App\Http\Request;
abstract class TestCase extends Illuminate\Foundation\Testing\TestCase{
// just copy Illuminate\Foundation\Testing\TestCase `call` method
// and set right Request class
public function call($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
{
$kernel = $this->app->make('Illuminate\Contracts\Http\Kernel');
$this->currentUri = $this->prepareUrlForRequest($uri);
$this->resetPageContext();
$request = Request::create(
$this->currentUri, $method, $parameters,
$cookies, $files,
array_replace($this->serverVariables, $server),
$content
);
$response = $kernel->handle($request);
$kernel->terminate($request, $response);
return $this->response = $response;
}
}
and don't forget to switch Illuminate\Http\Request::capture() to App\Http\Request::capture() in public/index.php file and to add $app->alias('request', 'App\Http\Request'); after or inside $app = require_once __DIR__.'/../bootstrap/app.php';
Yerkes answer inspired me to write a custom class, for use with pagination, but only on specific requests
<?php
namespace App\Http\Requests;
use Illuminate\Http\Request;
class PaginatedRequest extends Request
{
public function page(): int
{
return max(1, (int) ($this['page'] ?? 1));
}
public function perPage(): int
{
$perPage = (int) ($this['per_page'] ?? 100);
return max(1, min($perPage, 500));
}
public function offset(): int
{
return ($this->page() - 1) * $this->perPage();
}
}
I then also had to register a new ServiceProvider in /config/app.php, which looks like
<?php
namespace App\Providers;
use App\Http\Requests\PaginatedRequest;
use Illuminate\Support\ServiceProvider;
class PaginatedRequestServiceProvider extends ServiceProvider
{
public function boot()
{
$this->app->resolving(PaginatedRequest::class, function ($request, $app) {
PaginatedRequest::createFrom($app['request'], $request);
});
}
}
Now I can simply inject the PaginatedRequest in my controller methods only when I need it
<?php
namespace App\Http\Controllers;
use App\Http\Requests\PaginatedRequest;
class MyController
{
public function __invoke(PaginatedRequest $request)
{
$request->page();
// ...
}
}
I was able to add custom request object using FormRequest in Laravel 5.5 as follows.
First, just create FormRequest:
php artisan make:request MyRequest
Then just make it look like this:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MyRequest 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 [
//
];
}
}
You can then use MyRequest as drop-in replacement in any function that takes Request as parameter:
public function get(MyRequest $request)
{
}
I do realize that FormRequests are actually meant to be used for a different purpose, but whatever works.
Documentation on FormRequest: https://laravel.com/docs/5.0/validation#form-request-validation
In Laravel 5.5.x, I was able to extend the Request class for specific requests using the following:
ExtendedRequest class
<?php declare(strict_types = 1);
namespace App\Http\Request;
use Illuminate\Http\Request;
class ExtendedRequest extends Request {
public function hello() {
echo 'hello world!';
}
}
ExtendedRequestServiceProvider
<?php declare(strict_types = 1);
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Http\Request\ExtendedRequest;
class ExtendedRequestServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(ExtendedRequest::class, function ($app) {
return ExtendedRequest::capture();
});
}
}
Then in your Controllers
<?php
namespace App\Controllers;
use App\Http\Request\ExtendedRequest;
class TestController extends Controller
{
public function get(ExtendedRequest $request) {
echo $request->hello();
}
}
Hope this helps someone.

Categories