Functional testing a controller called via twig render method - php

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?

Related

Laravel: Is there a way to reuse a method that is using a Request variable as parameter?

I want to reuse my method store that is in generar\productoController
public function store(Request $request){}
and I want to reuse it in this class adquisicion\ComprasController, I know that I have to import the class to use the method i want, but the problem is the $request variable, should I create a new object of it with $request = new Request(), adding the data I want with this and sending it as parameter?
Thx for the help I'm really new with laravel
you can try it like this $this->store(request(),$otherData)
use the helper to get the current object of request
You can pass Request data to other method
productoController(Request $request){
// anything here
return redirect('your route name')->with('data', $request->all());
}
Here are two ways that can make methods reusable in laravel application:
Make a helper method
Create a Helpers folder in app folder, and create all static methods inside a helper.php
Helper.php
namespace App\Helpers;
class Helper {
public static function store() {
$request = request();
// ....
}
}
YourController.php
namespace App\Repositories;
use App\Helpers\Helper;
use Illuminate\Http\Request;
class YourController extends Controller
{
public function store(Request $request) {
// call the store method as
Helper::store();
}
}
The downside here is you will mix up all the non-related helper methods here and may difficult to organize.
Repository
You can use a Repository Pattern to architect your application, for example, if you store a foo object to your datastore, then you can first create Repositories folder in app folder, and create FooRepository.php in Repositories folder:
FooRepository.php
namespace App\Repositories;
class FooRepository {
public function store() {
$request = request();
// ...
}
}
YourController.php
namespace App\Http\Controllers;
use App\Repositories\FooRepository;
use Illuminate\Http\Request;
class YourController extends Controller
{
private $fooRepository = null;
public function __construct(FooRepository $fooRepository) {
parent::__construct();
$this->fooRepository = $fooRepository;
}
public function store(Request $request) {
// call the method as
$this->fooRepository->store();
}
}

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();
}
}

Laravel 5: Controller Testing - PHPUnit returns green

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.

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.

Use crawler in controller

// src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php
namespace Acme\DemoBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class DemoControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request('GET', '/demo/hello/Fabien');
$this->assertGreaterThan(0, $crawler->filter('html:contains("Hello Fabien")')->count());
}
}
this working OK in my tests, but i would like use this crawler also in controller. How can i do it?
i make route, and add to controller:
<?php
// src/Ens/JobeetBundle/Controller/CategoryController
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Acme\DemoBundle\Entity\Category;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class CategoryController extends Controller
{
public function testAction()
{
$client = WebTestCase::createClient();
$crawler = $client->request('GET', '/category/index');
}
}
but this return me error:
Fatal error: Class 'PHPUnit_Framework_TestCase' not found in /acme/vendor/symfony/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php on line 24
The WebTestCase class is a special class that is designed to be run within a test framework (PHPUnit) and you cannot use it in your controller.
But you can create a HTTPKernel client like this:
use Symfony\Component\HttpKernel\Client;
...
public function testAction()
{
$client = new Client($this->get('kernel'));
$crawler = $client->request('GET', '/category/index');
}
Note that you will only be able to use this client to browse your own symfony application. If you want to browse an external server you will need to use another client like goutte.
The crawler created here is the same crawler returned by WebTestCase so you can follow the same examples detailed in the symfony testing documentation
If you need more information, here is the documentation for the crawler component and here is the class reference
You shouldn't use WebTestCase in prod environment, because WebTestCase::createClient() creates test client.
In your controller you should do something like this (I recommend you to use Buzz\Browser):
use Symfony\Component\DomCrawler\Crawler;
use Buzz\Browser;
...
$browser = new Browser();
$crawler = new Crawler();
$response = $browser->get('/category/index');
$content = $response->getContent();
$crawler->addContent($content);

Categories