How to assert paginations in Laravel? - php

I have a category model with the following method:
public static function index()
{
return self::has('posts')->paginate(1);
}
My category controller:
public function index()
{
$categories = Category::index();
return view('categories.index', compact('categories'));
}
This is what I've tried, I am using RefreshDatabase trait.
public function test_index_view_is_working()
{
factory(Post::class, 5)->create();
$response = $this->get(route('categories.index'));
$response->assertViewHas('categories', Category::index());
}
This test fails for some reason:
Failed asserting that two objects are equal.
at tests/Feature/CategoryTest.php:38
37| $response->assertViewIs('categories.index');
> 38| $response->assertViewHas('categories', Category::index());
--- Expected
+++ Actual
## ##
'dispatchesEvents' => Array ()
'observables' => Array ()
'relations' => Array (
+ 'posts' => Illuminate\Database\Eloquent\Collection Object (...)
)
'touches' => Array ()
'timestamps' => true

The reason you get this error is because somehow the posts are eager loaded from the view/controller but not from the tests.
I'm guessing return self::has('posts')->with('posts')->paginate(1); could fix it.
Alternatively, you can test if you have the pagination at the bottom the page. Since {{ $categories->links() }} will add something like Previous and Next you can still look for it.
$response = $this->get(route('categories.index'));
$response->assertSee('Next');
Also, you can ensure that you paginate the categories but it won't ensure you have added the links at the bottom of the page.
use Illuminate\Contracts\Pagination\Paginator;
...
$response = $this->get(route('categories.index'));
$this->assertInstanceOf(Paginator::class, $response->viewData('categories'));

Are you running any migrations/factories in the setUp method of the test?
It looks like maybe there are no post records in your database so $categories is coming into the view as null.
Also side note if all you want to do is make sure that the view has the variable $categories you can use $response->assertViewHas('categories');. This is not ideal if you are wanting to make sure your view is getting actual data.

Related

Laravel testing pagination with less data

I'm trying to lower my test times. Currently I have a test where I need to test the pagination links and meta data.
In my controller my pagination is set to 15 however in my test I have to create 16 instances using a factory to be able to assert the data on page 2.
TestGetStudents.php
public function testGetStudents() {
Students::saveMany(factory(Student::class, 16)->make());
$this->get('url/students/list?page=2')
->assertJson([
'meta' [
'current_page' => 2
]
]);
}
StudentController.php
public function list() {
return Students::paginate();
}
How do I write the test without having to create 16 students to test data on the 2nd page?
Replace your controller to receive a paginate parameter from user:
public function list(Request $request) {
$per_page = !($request->input('per_page')) ? 15 : $request->input('per_page');
return Studentes::paginate($per_page);
}

Yii2 ArrayHelper::toArray doesn't work recursively

Yii2 ArrayHelper's helper method toArray doesn't convert nested objects.
Here is my test code.
public function actionTest()
{
$product = \common\models\Product::find()
->where(['id' => 5779])
->with('firstImage')
->one();
$product = \yii\helpers\ArrayHelper::toArray($product);
print_r($product);
}
Recursive property is enabled by default.
public static array toArray ( $object, $properties = [], $recursive =
true)
So this piece of code should work correctly but it doesn't.
Action returns one level array without firstImage object.
What I'm doing wrong here?
PS:
Code was simplified for test purposes. I know that in this certain situation one can simply use asArray() method to get AR record in array.
You should use this instead :
$product = \common\models\Product::find()
->where(['id' => 5779])
->with('firstImage')
->asArray()
->one();
Read more about Retrieving Data in Arrays.
If your really want to use toArray(), and since a relation is not really an attribute or property, you should simply use the second parameter, e.g. :
$product = \yii\helpers\ArrayHelper::toArray($product, [
'common\models\Product' => [
// add needed properties here
// ...
'firstImage',
],
]);
Or, if you are using REST, you could override extraFields() in your model :
public function extraFields()
{
return ['firstImage'];
}
Read more about REST fields.

laravel route with parameter not coming from url

I have multiple routes that look like this:
Route::get('pending-submit', 'CasesController#cases');
Route::get('submited', 'CasesController#cases');
Route::get('closed', 'CasesController#cases');
I have been looking around even in the router API documentation and I can't find a solution for my requirement other than creating multiple methods within the controller. The method does the exact same query except for adding a where clause to identify the different status between each case, what I was trying to do is have a method like this
public function cases($whereStatus = 0){
return Cases::where('status', $whereStatus)->get();
}
Instead of doing this:
public function pendingCases(){
return Cases::where('status', 0)->get();
}
public function submitedCases(){
return Cases::where('status', 1)->get();
}
public function closedCases(){
return Cases::where('status', 2)->get();
}
But I can figure a way to pass that parameter to the method from the route so I now have to create a method for each route which does not seem necessary to me. I understand I could just generate urls with the get parameter in it but I wanted to make that cleaner, is there a way for me to add that parameter without having it in the url?
By the way, I also tried something like this which did not wok:
Route::get(
'pending-submit',
array(
'uses' => 'CasesController#cases',
'params' => array(
'filter' => 0
)
)
);
EDIT:
I understand I can make URLs like https://someurl.com/cases?status=0 and can also have URLs like https://someurl.com/cases which require a different method per route however what I want is have URLs like https://someurl.com/cases and have a single method where the parameter is passed by the router instead of me getting it from the request so I can do it like this:
public function myMethod($param){
/*
* Here I access the $param without calling Request::input('someparam');
* or without Request::path() where then I have to check what path is it
*/
echo $param; /* this should already have the param from the route */
}
EDIT:
#AndyNoelker what I have is 3 different values either 0, 1 or 2
I want to have something like this
Route::get(
'cases',
array(
'uses' => 'CasesController#cases',
'status' => 0 /* this is what I need */
)
);
If not possible from the routes.php it is fine, I just want to know, all other methods you are giving me is not what I want or asking for since I already know how to do those.
You are going to have to pass the desired status in through the URL - otherwise the route will have no way of knowing which status you desire. You can either do it through URL query parameters or as a fully-fledged route parameter. I would personally suggest using a query parameter in this case, but I'll show you both.
Using Query parameters
URL
example.com/cases?status=1
Routes
Route::get('cases', CasesController#cases);
CasesController
public method cases(Request $request)
{
$input = $request->all();
$status = $input['status'];
return Cases::where('status',$status)->get();
}
Using Route parameters
URL
example.com/cases/1
Routes
Route::get('cases/{id}', CasesController#cases);
CasesController
public method cases($id)
{
return Cases::where('status',$id)->get();
}
Of course if you'd prefer that they use a slug or something other than a unique id in the route, then you'd have to adjust for that in your query, but this should give you the right idea.
I think you're looking for route parameters:
Route::get("/cases/{case}", "CasesController#cases");
This will match any of the following:
some_url/cases/pending-submit
some_url/cases/submited
some_url/cases/closed
...
Then, your function public function cases in CasesController would look like this:
public function cases($case){
if($case == "pending-submit"){
// Do Something
} else if($case == "submited") {
// Do Something Else
}
// OR
return Cases::where('status', $case)->get();
}
Etc etc. Look more into url parameters here: Documentation
Route::get('pending-submit', array('status' => 0, 'uses' => function(){
$CasesController = $app->make('CasesController');
return $CasesController->callAction('cases', $parameters = array());
}));
If I am understanding your question correctly, this should work.
Web Route
Route::get('pending-submit', [
'as' => 'pending',
'uses' => 'CasesController#cases',
'status-id' => '0'
]);
And, you can access the parameter passed with the route like below,
In Controller
$request->route()->getActions('status-id')
In View
{{ request()->route()->getAction('status-id') }}
Tested and perfectly works in L5.6

Dynamically generate URL in Laravel

I am using Laravel 4 for a new project, which is a news site.
The URL for a single article should be like this:
domain.com/{category}/{shorturl}/{id}
(for example "domain.com/sports/realmadrid-barcelona/15")
In my routes.php file I have declared:
Route::get('{cat}/{shorturl}/{id}', 'ArticlesController#view');
which is working fine.
Now all I need to do is use a single Article (model) instance to pass as parameter in the Controller action to generate the URL/Route. This means, instead of passing all three parameters ('cat', 'shorturl' and 'id'), I would like to pass the Article instance.
To manage this, what I did so far is the following:
In routes.php:
Route::model('article', 'Article');
Route::get('{cat}/{shorturl}/{id}', 'ArticlesController#view');
Route::get('article/{article}', 'ArticlesController#generateUrl');
In ArticlesController.php:
public function view($cat, $urltext, $id)
{
$article = Article::findOrFail($id);
return View::make('articleView', compact(array('article')));
}
public function generateUrl(Article $article)
{
$cat = $article->category()->pluck('text');
$shorturl = $article->urltext;
$id = $article->id;
return Redirect::action('ArticlesController#view', array('cat' => $cat, 'shorturl' => $shorturl, 'id' => $id));
}
By doing this, in my view file I have something like this to produce links to other articles:
{{ $otherarticle->title }}
The problem is that, although the redirect works, the actual URL (the one shown on mouse hover) is the 'original' one (this means: "domain.com/article/123") instead of the intended ("domain.com/sports/realmadrid-barcelona/123").
Any ideas on how to accomplish this? I would like to only use $article (the Article model instance) to generate URLs, in order to keep the code as simple and clean as possible.
Thank you,
Ilias
Instead of using a redirect you need to generate the real url right away.
I would add this method to your model class
class Article extends Eloquent {
public function getUrl(){
$cat = $this->category()->pluck('text');
$shorturl = $this->urltext;
$id = $this->id;
return URL::action('ArticlesController#view', array('cat' => $cat, 'shorturl' => $shorturl, 'id' => $id));
}
}
And then just call it on the article object $article->getUrl() to generate the url

Showing custom SQL results

What do I want to do
I want to list data that is pulled from the database with a certain condition.
What do I have and what does it do
I have a function that calls the data. When I print_r the data, it throws the correct stuff, so the query is executing directly. However, the display isn't working. It shows all the data in the database.
Here is my function:
public function myfunction() {
$adminExtensions = $this->AdminExtension->find('all',
array(
'conditions' => array('location_id'=>'3')
)
);
//print_r($adminExtensions);
$this->set('adminExtensions', $this->paginate());
}
What is the problem
The problem, as stated, is that it doesn't list just the records with location_id == 3. It lists everything.
I have narrowed it down to the last line of the function, but I can't seem to get the right code in there.
My display file (myfunction.ctp) is a basic baked cakePHP index file.
What am I doing wrong?
The code you currently have calls two different find operations. $this->AdminExtension->find() will return an array with all the AdminExtensions with a location_id of 3. The second $this->paginate() call just returns all possible results suitable for pagination in the view.
If you want to filter the paginated results you have to either configure the $paginate variable in the Controller or do it directly before you call $this->paginate.
class PostsController extends AppController {
public $paginate = array(
'conditions' => array('location_id'=>'3')
);
}
This will adjust pagination for all $this->paginate calls in the controller.
To do it for only one paginate call:
public function your_view() {
$this->set('adminExtensions', $this->paginate('AdminExtension', array('location_id' => '3')));
);

Categories