Laravel PHPUnit unable to test GET parameters - php

I have a Laravel 8 app that I'm working on where I need to use GET parameters (e.g. test?num=1) but unfortunately, I don't appear to be able to figure out how to run the unit tests against this.
I've followed the guidance I can find online (including Laravel phpunit testing get with parameters, which looked very promising) however, none of the approaches seem to work.
To try and figure out the issue, I've created a VERY simple version and I'm getting the same behaviours. If I go to the URL directly it works as expected but the unit tests fail.
routes\web.php
Route::get('/test', 'TimeController#test')->name('myTest');
app\Http\Controllers\TimeController.php
public function test()
{
return $_GET['num'] ?? "Num not provided";
}
test\Unit\Http\Controllers\TimeControllerTest.php
/** #test */
public function num_test_1()
{
$params = ["num" => "1"];
$response = $this->get('/test?num=1');
$response->assertSeeText("1");
}
/** #test */
public function num_test_2()
{
$params = ["num" => "1"];
$response = $this->get('/test', $params);
$response->assertSeeText("1");
}
/** #test */
public function num_test_3()
{
$params = ["num" => "1"];
$response = $this->call('GET', '/test', $params);
$response->assertSeeText("1");
}
All three of the above tests fail with the message
Failed asserting that 'Num not provided' contains "1".
I have even gone to the lengths of deleting the node_modules and vendor folders just in case I had a corrupt PHPUnit package or something.
Am I missing anything obvious? This feels like something that should be pretty easy to do and something that a lot of people will have done before.

It work fine.
You should change your method like this:
public function test(Request $request)
{
return $request->input('num') ?? "Num not provided";
}
Or with query method:
public function test(Request $request)
{
return $request->query('num') ?? "Num not provided";
}
You can also pass your desired default value if none found instead of using ??:
public function test(Request $request)
{
return $request->input('num', 'Num not provided');
}
public function test(Request $request)
{
return $request->query('num', 'Num not provided');
}

Related

Laravel Validator Not Returning Key

I am creating a new API call for our project.
We have a table with different locales. Ex:
ID Code
1 fr_CA
2 en_CA
However, when we are calling the API to create Invoices, we do not want to send the id but the code.
Here's a sample of the object we are sending:
{
"locale_code": "fr_CA",
"billing_first_name": "David",
"billing_last_name": "Etc"
}
In our controller, we are modifying the locale_code to locale_id using a function with an extension of FormRequest:
// This function is our method in the controller
public function createInvoice(InvoiceCreateRequest $request)
{
$validated = $request->convertLocaleCodeToLocaleId()->validated();
}
// this function is part of ApiRequest which extend FormRequest
// InvoiceCreateRequest extend ApiRequest
// So it goes FormRequest -> ApiRequest -> InvoiceCreateRequest
public function convertLocaleCodeToLocaleId()
{
if(!$this->has('locale_code'))
return $this;
$localeCode = $this->input('locale_code');
if(empty($localeCode))
return $this['locale_id'] = NULL;
$locale = Locale::where(Locale::REFERENCE_COLUMN, $localeCode)->firstOrFail();
$this['locale_id'] = $locale['locale_id'];
return $this;
}
If we do a dump of $this->input('locale_id') inside the function, it return the proper ID (1). However, when it goes through validated();, it doesn't return locale_id even if it's part of the rules:
public function rules()
{
return [
'locale_id' => 'sometimes'
];
}
I also tried the function merge, add, set, etc and nothing work.
Any ideas?
The FormRequest will run before it ever gets to the controller. So trying to do this in the controller is not going to work.
The way you can do this is to use the prepareForValidation() method in the FormRequest class.
// InvoiceCreateRequest
protected function prepareForValidation()
{
// logic here
$this->merge([
'locale_id' => $localeId,
]);
}

Tenant Multi tenancy Laravel Unit Testing issue

Here is the test case that i created for category testing. I am getting 404 on this route while i have correctly configured the tenant test case and this route is exist on subdomain that was created on chrome browser.
public function test_example()
{
$response = $this->call('GET', '/categories/6/edit');
$this->assertEquals(200, $response->getStatusCode(),$response->exception->getMessage());
}
My TestCase.php
protected $tenancy = false;
public function setUp(): void
{
parent::setUp();
if ($this->tenancy) {
$this->initializeTenancy();
}
}
public function initializeTenancy()
{
$tenant = Tenant::create();
tenancy()->initialize($tenant);
}
Documentation I am following
https://tenancyforlaravel.com/docs/v3/testing
Result:
I want 302 response means redirect to login code.
I hope this piece of code can help you
https://github.com/archtechx/tenancy/issues/635#issuecomment-939226522
tenancy()->initialize($tenant);
URL::forceRootUrl('http://' . $tenant->domains[0]['domain']);

Why view::make doesn't work when called from a different function in controller?

This is a fairly simple question, and I made this work in other controllers, but I don't seem to be able to figure out what's exactly going on in this specific case, and why it's not working.
I have this two functions in my controller:
public function create(Request $request)
{
//
$this->edit($request, null);
}
public function edit(Request $request, Group $group = null)
{
//
return View::make('groups.create')
->with('controllerUrl', $this->controllerUrl)
->with('record', $group);
}
In this example the create function sends me to a blank page.
This is my route:
Route::group(['middleware'=>['web', 'CheckWritePermission']], function ()
{
Route::resource('some_model', 'SomeModelController');
Route::resource('model', 'ModelController');
Route::resource('groups', 'GroupController');
});
For some reason doing it like this in other controllers works, and for some other it doesn't in this case.
I'm very confused as to why this is, because doing it like this works just fine.
public function create(Request $request)
{
//
// $this->edit($request, null);
return View::make('groups.create')
->with('controllerUrl', $this->controllerUrl)
->with('record', $group);
}
I just want to understand why in some cases it works and in others it doesn't.
You are missing a return statement
return $this->edit($request, null);
your edit method does return something, but your create method doesn't, therefore page stays blank

Laravel Object Oriented

I am somewhat new to OOP, although I know about interfaces and abstract classes a bit. I have a lot of resource controllers that are somewhat similar in the bigger scheme of things, they all look like the example below, the only main difference is the index and what I pass to the index view.
What I simply need to know is, can I OO things up a bit with my resource controllers? For example, create one "main" resource controller in which I simply pass the correct instances using an interface for example? I tried playing around with this but I got an error that the interface wasn't instantiable, so I had to bind it. But that means I could only bind an interface to a specific controller.
Any advice, tips and pointers will help me out :)
class NotesController extends Controller
{
public function index()
{
$notes = Note::all();
return view('notes.index', compact('notes'));
}
public function create()
{
return view('notes.create');
}
public function show(Note $note)
{
return view('notes.show', compact('note'));
}
public function edit(Note $note)
{
return view('notes.edit', compact('note'));
}
public function store(Request $request, User $user)
{
$user->getNotes()->create($request->all());
flash()->success('The note has been stored in the database.', 'Note created.');
return Redirect::route('notes.index');
}
public function update(Note $note, Request $request)
{
$note->update($request->all());
flash()->success('The note has been successfully edited.', 'Note edited.');
return Redirect::route('notes.index');
}
public function delete($slug)
{
Note::where('slug', '=', $slug)->delete();
return Redirect::to('notes');
}
}
Note: Totally my opinion!
I would keep them how you have them. It makes them easier to read and understand later. Also will save you time when you need to update one to do something different from the rest. We tried this in a project I worked on and while granted it wasn't the best implementation, it is still a pain point to this day.
Up to you though. I'm sure people have done that in a way that they love and works great. Just hasn't been the case in my experience. I doubt anyone would look at your code though and criticize you for not doing it.
In Case you need to bind different Model instanses then you may use Contextual Binding, for example, put the following code in AppServiceProvider's register() method:
$this->app->when('App\Http\Controllers\MainController')
->needs('Illuminate\Database\Eloquent\Model')
->give(function () {
$path = $this->app->request->path();
$resource = trim($path, '/');
if($pos = strpos($path, '/')) {
$resource = substr($path, 0, $pos);
}
$modelName = studly_case(str_singular($resource));
return app('App\\'.$modelName); // return the appropriate model
});
In your controller, use a __construct method to inject the model like this:
// Put the following at top of the class: use Illuminate\Database\Eloquent\Model;
public function __construct(Model $model)
{
$this->model = $model;
}
Then you may use something like this:
public function index()
{
// Extract this code in a separate method
$array = explode('\\', get_class($this->model));
$view = strtolower(end($array));
// Load the result
$result = $this->model->all();
return view($view.'.index', compact('result'));
}
Hope you got the idea so implement the rest of the methods.

testing php laravel controller shouldReceive arguments

I have a laravel model which uses ardent/eloquent. I am trying to set up tests for the controller in particular, storing a new model that uses the ardent model.
The method works in the app but I'm having trouble with my tests
I'm having problems working out how to mock the calls this method makes.
My controllers set up and the method in question is this one:
use golfmanager\service\creator\TicketCreatorInterface;
//controller manages the ticket books
class BooksController extends BaseController {
/**
* Book Repository
*
* #var Book
*/
protected $book;
protected $ticket;
public function __construct(Book $book, TicketCreatorInterface $ticket)
{
$this->book = $book;
$this->ticket = $ticket;
}
public function store()
{
$input = Input::all();
$result = $this->book->save();
if ($result) {
//if book created then create tickets
$this->ticket->createTicket($input, $this->book);
return Redirect::route('books.index');
}
return Redirect::route('books.create')
->withInput()
->withArdentErrors()
->with('message', 'There were validation errors.');
}
And the methods used by the interface (TicketCreator):
public function createTicket($input, $book) {
//dd($input);
$counter = $input['start_number'];
while($counter <= $input['end_number']) {
$ticketDetails = array(
'ticketnumber'=>$counter,
'status'=>'unused',
'active'=>1
);
$this->ticket->create($ticketDetails)->save();
$this->ticket->book()->associate($book)->save();
$counter = $counter+1;
}
return $counter;
}
My test is as follows:
use Mockery as m;
use Way\Tests\Factory;
class BooksTest extends TestCase {
public function __construct()
{
$this->mock = m::mock('Ardent', 'Book');
$this->collection = m::mock('Illuminate\Database\Eloquent\Collection')->shouldDeferMissing();
}
public function setUp()
{
parent::setUp();
$this->attributes = Factory::book(['id' => 1, 'assigned_date'=> '20/11/2013']);
$this->app->instance('Book', $this->mock);
}
public function testStore()
{
Input::replace($input = ['start_number'=>1000, 'end_number'=>1010, 'assigned_date'=>'20/11/2013']);
$this->mock->shouldReceive('save')->once()->andReturn(true);
$this->ticket->shouldReceive('createTicket')->once()->with($input, $this->mock)->andReturn(true);
//with($input);
//$this->validate(true);
$this->call('POST', 'books');
$this->assertRedirectedToRoute('books.index');
}
Currently I get an error:
No matching handler found for Book::save()
Is this being thrown because the book model doesnt contain a save method? If it is how do I mock the model correctly. I don't want it to touch the database (although it could if it has to).
Is it the multiple saves in the createTicket method?
Still learning how to set up tests correctly - slowly getting there but not enough knowledge yet.
If I change the name of the method in shouldReceive to say 'store' it still comes up with the save() error message.
Update:
I have isolated part of the problem to the createTicket call. I've changed my testStore test and updated as above.
My error with this current test is: Undefined index: start_number.
If I remove the call to createTicket in the controller method I don't get an error. I tried using Input::replace to replace the input from a form but appears not getting through to my function
How can I simulate a form input in the mocked objects?
Thanks

Categories