Laravel Controller Layout testing - php

I'm trying to include unit-testing in a new Laravel app I'm building.
Right now I'm want to test my OrderController. The index method of this controller looks like this:
public function index()
{
// We need the extra 'orders()' for the query scope
$orders = $this->order->orders()->paginate($this->perPage);
$this->layout->content = View::make('orders.index', compact('orders'));
}
Now I have my test that looks like this:
public function testIndex()
{
$this->call('GET', 'orders');
$this->assertResponseOk();
$this->assertViewHas('orders');
}
Now if I run phpunit, the test does run, but I'm getting: 1) OrderControllerTest::testIndex
Failed asserting that an array has the key 'orders'.
I've tracked down the issue to using Controller Layouts $this->layout.
If I just do return View::make() the test does pass, if I return $this->layout... it also does pass, but this destroys the actual app.
So only option I've found is to use return View::make() and have #extends('master') in that view. But it's seems strange to me that you can't use Controller Layouts in your app if you want to test it.
Am I doing something wrong?

Edit:
I had the same problem and here is slightly messy solution that works!
View::composer('ordersviewname', function($view) {
$this->assertArrayHasKey('orders', $view->getData());
});
$this->call('GET', 'orders');
Note that you have to put this code BEFORE $this->call()
EDIT:
Here is more elegant solution!
Add functions to TestCase
protected $nestedViewData = array();
public function registerNestedView($view)
{
View::composer($view, function($view){
$this->nestedViewsData[$view->getName()] = $view->getData();
});
}
/**
* Assert that the given view has a given piece of bound data.
*
* #param string|array $key
* #param mixed $value
* #return void
*/
public function assertNestedViewHas($view, $key, $value = null)
{
if (is_array($key)) return $this->assertNestedViewHasAll($view, $key);
if ( ! isset($this->nestedViewsData[$view]))
{
return $this->assertTrue(false, 'The view was not called.');
}
$data = $this->nestedViewsData[$view];
if (is_null($value))
{
$this->assertArrayHasKey($key, $data);
}
else
{
if(isset($data[$key]))
$this->assertEquals($value, $data[$key]);
else
return $this->assertTrue(false, 'The View has no bound data with this key.');
}
}
/**
* Assert that the view has a given list of bound data.
*
* #param array $bindings
* #return void
*/
public function assertNestedViewHasAll($view, array $bindings)
{
foreach ($bindings as $key => $value)
{
if (is_int($key))
{
$this->assertNestedViewHas($view, $value);
}
else
{
$this->assertNestedViewHas($view, $key, $value);
}
}
}
public function assertNestedView($view)
{
$this->assertArrayHasKey($view, $this->nestedViewsData);
}
Now you would rewrite your test
$view='orders';
$this->registerNestedView($view);
$this->call('GET', 'orders');
$this->assertNestedViewHas($view, 'orders');
assertNestedViewHas() has all the same feautures as assertViewHas() !
I added another function assertNestedView() which just check if given view was called.

Related

Laravel - associative Array validation in a custom request

I have a customer request as follows:
<textarea name="intro[en]"></textarea>
<textarea name="intro[fr]"></textarea>
<textarea name="intro[de]"></textarea>
I am validating it with a custom request:
class UpdateProfileRequest extends Request
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'intro.*' => 'required|max:100'
];
}
}
The validator is not working. I think this is because the .* only works for numbered arrays, rather than associative arrays?
I'm not sure how to go about doing this.
Is there a way to do it with a custom request like this? If so what is the syntax?
Otherwise, what should I do. I already wrote some custom code inside the controller method like this:
$hasIntro = false;
$hasBio = false;
foreach($request->get('intro') as $language => $localIntro)
{
if(!empty($request->get('intro')[$language]))
{
$hasIntro = true;
}
}
if(!$hasIntro or !$hasBio)
{
return redirect()->back()->withErrors('You must enter at least 1 Bio and 1 Intro');
}
Which I think might be one manual way of going about this. Though I believe withErrors requires a validator, so I'm back to the same problem... Though perhaps there is a way to do this manually?
My ideal solution is to find the associative array syntax, if that indeed exists?
I'm not sure about the right way
but my idea is something like this
public function rules($inputs)
{
$rules = [];
foreach ($inputs as $key => $val) {
if ( strpos($key, "intro") === 0 ){
$rules[$key] = 'required|max:100';
}
}
return $rules;
}
class UpdateProfileRequest extends Request
{
/**
* 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 [
'intro.*' => 'required|max:100'
];
}
/**
* #param Validator $validator
*
* #return mixed
*/
protected function formatErrors(Validator $validator)
{
return $validator->errors()->all();
}
}
You have below same name so make sure it's different or remove one, change name.
<textarea name="intro[fr]"></textarea>
<textarea name="intro[fr]"></textarea>
public function rules()
{
$rules = [];
$intro = $this->request->get('intro');
if (!empty($intro)) {
foreach ($intro as $index => $doc) {
$rules[sprintf('intro.%d', $index)] = 'required|max:100';
}
}
return $rules;
}

Calling a function inside a function [PHP, Laravel, Eloquentl]

I am using October CMS built on Laravel, and I am having some strange issues I am not sure how to interpret.
Code Sample 1: Works fine (Component EstateList)
public function onRun()
{
$this->listEstates();
}
/**
* Pulls all the estates from the model
*
* #return $estateList
*/
protected function listEstates()
{
$estateList = RealEstate::all();
return $estateList;
}
Code Sample 2: Doesn't work (Component EstateDetails)
public function onRun()
{
$this->show();
}
/**
* Returns the slug and display individual Estate Object
*
* #return $pageDetails
*/
protected function show()
{
$slug = $this->param('slug');
$pageDetails = RealEstate::find($slug);
echo $slug; //returns slug as it should
echo $pageDetails; //empty
if ($pageDetails) {
return $pageDetails;
} else {
return \Response::make('Page not found', 404);
}
}
If I just put the code of show() into the function onRun() it works fine. Why does echo echo $pageDetails return empty on the Code Sample 2? if it is ran in a seperate function show().
Thank you for your help.
Try changing it to RealEstate::where('slug', '=', $slug)->firstOrFail();. The find bit searches the ID table for the column, not the slug.
You are not 'returning' the Response from show()
Try changing $this->show(); to return return $this->show(); in onRun()
Try changing your code to this
public function onRun()
{
return $this->show();
}
/**
* Returns the slug and display individual Estate Object
*
* #return $pageDetails
*/
protected function show()
{
$slug = $this->param('slug');
$pageDetails = RealEstate::where('slug', '=', $slug)->firstOrFail();;
echo $slug; //returns slug as it should
echo $pageDetails; //empty
if ($pageDetails) {
return $pageDetails;
} else {
return \Response::make('Page not found', 404);
}
}
Hopefully This will solve your problem

How does one load gate defines when testing a Laravel application?

I'm writing tests for a Laravel application. In my AuthServiceProvider->boot(), I define a number of user abilities with $gate->define() based on a permissions table in my database.
Basically this:
foreach ($this->getPermissions() as $permission) {
$gate->define($permission->name, function ($user) use ($permission) {
return $user->hasPermission($permission->name);
});
}
In my tests I'm creating permissions on the fly, but the AuthServiceProvider has already booted up, which means I can't verify user permissions with #can, Gate, etc.
Is there a proper way to deal with this issue?
I know I'm a bit late for the party on this one, but still - I just had the same problem myself and hence this question doesn't have a comprehensive answer, here is my solution for the same issue (in Laravel 5.3):
I've got this in my app\Providers\AuthServiceProvider:
/**
* Register any authentication / authorization services.
*
* #param Gate $gate
*/
public function boot(Gate $gate)
{
$this->registerPolicies();
if (!app()->runningInConsole()) {
$this->definePermissions($gate);
}
}
/**
* #param Gate $gate
*/
private function definePermissions(Gate $gate)
{
$permissions = Permission::with('roles')->get();
foreach($permissions as $permission) {
$gate->define($permission->key, function($user) use ($permission) {
return $user->hasRole($permission->roles);
});
}
}
This takes care of the normal application flow when not testing and disables the premature policy registration when testing.
In my tests/TestCase.php file I have the following methods defined (note that Gate points to Illuminate\Contracts\Auth\Access\Gate):
/**
* Logs a user in with specified permission(s).
*
* #param $permissions
* #return mixed|null
*/
public function loginWithPermission($permissions)
{
$user = $this->userWithPermissions($permissions);
$this->definePermissions();
$this->actingAs($user);
return $user;
}
/**
* Create user with permissions.
*
* #param $permissions
* #param null $user
* #return mixed|null
*/
private function userWithPermissions($permissions, $user = null)
{
if(is_string($permissions)) {
$permission = factory(Permission::class)->create(['key'=>$permissions, 'label'=>ucwords(str_replace('_', ' ', $permissions))]);
if (!$user) {
$role = factory(Role::class)->create(['key'=>'role', 'label'=>'Site Role']);
$user = factory(User::class)->create();
$user->assignRole($role);
} else {
$role = $user->roles->first();
}
$role->givePermissionTo($permission);
} else {
foreach($permissions as $permission) {
$user = $this->userWithPermissions($permission, $user);
}
}
return $user;
}
/**
* Registers defined permissions.
*/
private function definePermissions()
{
$gate = $this->app->make(Gate::class);
$permissions = Permission::with('roles')->get();
foreach($permissions as $permission) {
$gate->define($permission->key, function($user) use ($permission) {
return $user->hasRole($permission->roles);
});
}
}
This enables me to use this in tests in multiple ways. Consider the use cases in my tests/integration/PermissionsTest.php file:
/** #test */
public function resource_is_only_visible_for_those_with_view_permission()
{
$this->loginWithPermission('view_users');
$this->visit(route('dashboard'))->seeLink('Users', route('users.index'));
$this->visit(route('users.index'))->assertResponseOk();
$this->actingAs(factory(User::class)->create());
$this->visit(route('dashboard'))->dontSeeLink('Users', route('users.index'));
$this->get(route('users.index'))->assertResponseStatus(403);
}
/** #test */
public function resource_action_is_only_visible_for_those_with_relevant_permissions()
{
$this->loginWithPermission(['view_users', 'edit_users']);
$this->visit(route('users.index'))->seeLink('Edit', route('users.edit', User::first()->id));
$this->loginWithPermission('view_users');
$this->visit(route('users.index'))->dontSeeLink('Edit', route('users.edit', User::first()->id));
}
This works just fine in all my tests. I hope it helps.
public function boot(GateContract $gate)
{
parent::registerPolicies($gate);
$gate->before(function($user, $ability) use ($gate){
return $user->hasPermission($ability);
});
}
I haven't extensively tested this, but it seems to work from my quick tests.
I'm not sure what the "proper" way (if there is one) to define a gate for testing. I couldn't find an answer for this after looking at the documentation and searching, but this seems to work in a pinch in Laravel 5.7:
Defining a gate in a model factory state:
$factory->state(App\User::class, 'employee', function () {
Gate::define('employee', function ($user) {
return true;
});
return [];
});
This test function will have both the 'employee' and the 'admin' gate applied since we are using the 'employee' state when creating the user:
/** #test */
public function an_admin_user_can_view_the_admin_page()
{
$user = factory('App\User')->state('employee')->make();
$this->actingAs($user);
Gate::define('admin', function ($user) {
return true;
});
$this->get('/admin')
->assertOk();
}
I know this is a really old question, but it was the top result in a search and hopefully can help someone.
Don't forget to use the Gate facade:
use Illuminate\Support\Facades\Gate;
You could do something like this inside AuthServiceProvider
First import the necessary packages
use Illuminate\Auth\Access\Gate;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
and then add this boot() method
public function boot(GateContract $gate)
{
parent::registerPolicies($gate);
$gate->define('update-post', function ($user, $post, $isModerator) {
// check if user id equals post user id or whatever
if ($user->id === $post->user->id) {
return true;
}
// you can define multiple ifs
if ($user->id === $category->user_id) {
return true;
}
if ($isModerator) {
return true;
}
return false;
});
// you can also define multiple gates
$gate->define('update-sub', function($user, $subreddit) {
if($user->id === $subreddit->user->id) {
return true;
}
return false;
});
And then in your controller you could do something like this
if (Gate::denies('update-post', [$post, $isModerator])) {
// do something
}

laravel 4 - route with two bound models

I need to check that {subcategory} has parent category {category}. How i can get the model of {category} in second binding?
I tried $route->getParameter('category');. Laravel throws FatalErrorException with message "Maximum function nesting level of '100' reached, aborting!".
Route::bind('category', function ($value) {
$category = Category::where('title', $value)->first();
if (!$category || $category->parent_id) {
App::abort(404);
}
return $category;
});
Route::bind('subcategory', function ($value, $route) {
if ($value) {
$category = Category::where('title', $value)->first();
if ($category) {
return $category;
}
App::abort(404);
}
});
Route::get('{category}/{subcategory?}', 'CategoriesController#get');
Update:
Now i made this, but i think it's not the best solution.
Route::bind('category', function ($value) {
$category = Category::where('title', $value)->whereNull('parent_id')->first();
if (!$category) {
App::abort(404);
}
Route::bind('subcategory', function ($value, $route) use ($category) {
if ($value) {
$subcategory = Category::where('title', $value)->where('parent_id', $category->id)->first();
if (!$subcategory) {
App::abort(404);
}
return $subcategory;
}
});
return $category;
});
You may try this and should work, code is self explanatory (ask if need an explanation):
Route::bind('category', function ($value) {
$category = Category::where('title', $value)->first();
if (!$category || $category->parent_id) App::abort(404);
return $category;
});
Route::bind('subcategory', function ($value, $route) {
$category = $route->parameter('category');
$subcategory = Category::where('title', $value)->whereParentId($category->id);
return $subcategory->first() ?: App::abort(404); // shortcut of if
});
Route::get('{category}/{subcategory?}', 'CategoriesController#get');
Update: (As OP claimed that, there is no parameter method available in Route class):
/**
* Get a given parameter from the route.
*
* #param string $name
* #param mixed $default
* #return string
*/
public function getParameter($name, $default = null)
{
return $this->parameter($name, $default);
}
/**
* Get a given parameter from the route.
*
* #param string $name
* #param mixed $default
* #return string
*/
public function parameter($name, $default = null)
{
return array_get($this->parameters(), $name) ?: $default;
}
I can't test this right now for you, but the closure function receives a $value and $route.
The last one is a instance of '\Illuminate\Routing\Route' (http://laravel.com/api/class-Illuminate.Routing.Route.html), and perhaps you could use the getParameter() method to retrieve some data....
I recently ran into same issues while trying to auto validate my stories existence inside my Session Model. So, i tried to check my Story model existence inside my Session Model using model bindings.
This is my solution
$router->bind('stories', function($value, $route) {
$routeParameters = $route->parameters();
$story = Story::where('id', $value)->where('poker_session_id', $routeParameters['poker_sessions']->id)->first();
if (!$story) {
throw new NotFoundHttpException;
}
return $story;
});
You can actually get route parameters using $route->parameters(), which returns an array. In my case, "poker_sessions" key contain an PokerSession Model, which i want.
Please be careful and use this only when you have a /category/{category}/subcategory/{subcategory} url like. Not subcategory without any {category}.
Good luck!.

accessing object and its relations in laravel 4.1

I hope I can explain this clearly, apologies in advance if it is confusing. I have a goals table which hasOne of each of bodyGoalDescs, strengthGoalDescs and distanceGoalDescs as shown below
goals.php
class Goal extends BaseModel
{
protected $guarded = array();
public static $rules = array();
//define relationships
public function user()
{
return $this->belongsTo('User', 'id', 'userId');
}
public function goalStatus()
{
return $this->hasOne('GoalStatus', 'id', 'goalStatus');
}
public function bodyGoalDesc()
{
return $this->hasOne('BodyGoalDesc', 'id', 'bodyGoalId');
}
public function distanceGoalDesc()
{
return $this->hasOne('DistanceGoalDesc', 'id', 'distanceGoalId');
}
public function strengthGoalDesc()
{
return $this->hasOne('StrengthGoalDesc', 'id', 'strengthGoalId');
}
//goal specific functions
public static function yourGoals()
{
return static::where('userId', '=', Auth::user()->id)->paginate();
}
}
each of the three tables looks like this with the function details changed
class BodyGoalDesc extends BaseModel
{
protected $guarded = array();
public static $rules = array();
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'bodyGoalDescs';
//define relationships
public function goal()
{
return $this->belongsTo('Goal', 'bodyGoalId', 'id');
}
}
a goal has either a body goal, a strength goal, or a distance goal. I am having a problem with this method in the controller function
<?php
class GoalsController extends BaseController
{
protected $goal;
public function __construct(Goal $goal)
{
$this->goal = $goal;
}
/**
* Display the specified resource.
*
* #param int $id
* #return Response
*/
public function show($id)
{
$thisgoal = $this->goal->find($id);
foreach ($this->goal->with('distanceGoalDesc')->get() as $distancegoaldesc) {
dd($distancegoaldesc->DistanceGoalDesc);
}
}
}
when I pass through goal 1 which has a distance goal the above method dies and dumps the Goal object with the details of goal 1 and an array of its relations including an object with DistanceGoalDes.
when I pass through goal 2 it passes through exactly the same as if I had passed through goal 1
if I dd() $thisgoal i get the goal that was passed through
what I want ultimately is a method that returns the goal object with its relevant goal description object to the view but this wont even show me the correct goal details not too mind with the correct relations
this function is now doing what I want it to do, I am sure there is a better way (besides the fact that its happening in the controller right now) and I would love to hear it.
public function show($id)
{
$thisgoal = $this->goal->find($id);
if (!$thisgoal->bodyGoalDesc == null) {
$goaldesc = $thisgoal->bodyGoalDesc;
return View::make('goals.show')
->with('goal', $thisgoal)
->with('bodygoaldesc', $goaldesc);
} elseif (!$thisgoal->strengthGoalDesc == null) {
$goaldesc = $thisgoal->strengthGoalDesc;
return View::make('goals.show')
->with('goal', $thisgoal)
->with('strengthgoaldesc', $goaldesc);
} elseif (!$thisgoal->distanceGoalDesc == null) {
$goaldesc = $thisgoal->distanceGoalDesc;
return View::make('goals.show')
->with('goal', $thisgoal)
->with('distancegoaldesc', $goaldesc);
}
}

Categories