How to test a controller with model injection but without middleware? - laravel

When I test this:
use WithoutMiddleware;
public function testPutSportOK()
{
$sport = Sport::first();
$sportName = 'Modification '.$sport->sport_name;
$position = random_int(0,100);
$post = [
'sport_name' => $sportName,
'position' => $position
];
$response = $this->json('PUT', '/api/sports/'.$sport->id, $post);
$response->assertStatus(200);
The test failed because I use the model injection in my controller. I understand that this injection needs the "bindings" middleware. But as I disabled all the middlewares, this injection cannot be done.
I disabled the middlewares for authentication reasons.
I tried to add this:
$this->withMiddleware('bindings');
But it's still the same.
How to test a controller using the model injection and without middlewares?
Edit
Add the controller with the model injection:
public function update(Request $request, Sport $sport)
{
// var_dump($sport);
$validator = Validator::make($request->all(), [
'sport_name' => 'required',
'position' => 'required|int'
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
try {
// not necessary with the injection model
// $sport = Sport::findOrFail($id);
$sport->fill($request->all());
$sport->save();
return new SportResource($sport);
} catch (\Exception $ex) {
return response()->json($ex->getMessage(), 400);
}
}

From the source code here, you can actually disable "some" of your middlewares (instead of disabling them all) by giving an array of middlewares you WANT to disable.
$this->withoutMiddleware([
\App\Http\Middleware\Authenticate,
\App\Http\Middleware\RedirectIfAuthenticated,
// Add more here
]);

Finally I gave up the idea of ​​model injection. To prefer the passage of a classic id between the route and the controller.
As this my PHPUnit + Postman tests work perfectly and I'm not bored with this "binding" middleware any more.
Certainly it requires to write one more line in the controller to read the corresponding model in database. But it's only one line. I accept it !

Related

laravel testing web routes

I have a web route for manufactures module (Backed route that handle resource)
Route::resource('/admin/manufactures', App\Http\Controllers\Back\ManufacturerController::class);
I have create a create a ManufacturerRequest with a simple rule name=>required
and i want to use Laravel Test to test the resource (CRUD)
In my controller the store method is as follow
public function store(ManufacturerRequest $request)
{
//db
$request->validate();
Manufacturer::create($request->all());
}
I have a simple test
$response = $this->post('/admin/manufactures' ,
[
'_token' => csrf_token(),
'name' => 'test'
]);
$response->assertStatus(200);
which is return 403, but besides that the store Method takes ManufacturerRequest object that handles validation, and in the case of the test i pass an array because it only accepts array.
So how can I create a Test that "simulate" form and pass request to controller in order to test validation and CRUD
What you want to do is very easy and is also explained on the Documentation, it is very important that you fully read the documentation so you have a rough idea of what you can do with the framework, specially because you are new with it.
As you did not specify which version you are using, I will be using Laravel 8, but it is roughly the same across the board.
Based on your code:
Resource route
Route::resource('/admin/manufactures', ManufacturerController::class);
Controller
public function store(ManufacturerRequest $request)
{
//db
$request->validate();
Manufacturer::create($request->all());
}
You need to change your controller to:
public function store(ManufacturerRequest $request)
{
//db
Manufacturer::create($request->all());
}
Yes, just remove the $request->validate(); as the framework will automatically resolve the FormRequest and authorize and validate. If you read part of the validate explanation you will see this:
So, how are the validation rules evaluated? All you need to do is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic.
So, when the first line of the controller is run, it means the FormRequest passed the authorization check and validated the input.
What you can also update on your controller is:
public function store(ManufacturerRequest $request)
{
//db
Manufacturer::create($request->validated());
}
See I have changed $request->all() with $request->validated(), validated will only return the fields you have a key on the FormRequest's rules, if you use all you will be passing everything you have on the request (also passing non-validated data and that is not good).
Before you try anything, I recommend you read my answer on this post, so you can have a clearer picture about testing.
So, you are getting a 403 maybe because you have a middleware asking for you to be logged in, and you did not use $this->actingAs().
Just because you did not share the FormRequest rules, I will just give a super small example. If you have a this rule inside:
'name' => ['required', 'string'],
What you can do to test that is:
public function test_manufacturer_is_created(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->post('/admin/manufactures', ['name' => $name = 'Manufacturer 1']);
$response->assertSuccessful();
$this->assertDatabaseHas(
'manufacturers',
[
'name' => $name
]
);
}
/**
* #depends test_manufacturer_is_created
*/
public function test_unauthorized_error_is_thrown_when_the_user_is_not_logged_in(): void
{
$response = $this->post('/admin/manufactures', ['name' => 'Manufacturer 1']);
$response->assertUnauthorized();
}
/**
* #depends test_manufacturer_is_created
* #dataProvider invalidDataProvider
*/
public function test_error_should_be_returned_when_invalid_data_is_sent($value, bool $deleteField): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->post(
'/admin/manufactures',
! $deleteField ? ['name' => $value] : []
);
$response->assertInvalid(['name']);
}
public function invalidDataProvider(): array
{
return [
'Missing name' => [null, true],
'Empty name' => ['', false],
'Null name' => [null, false],
'Array name' => [[], false],
'Number name' => [123, false],
'Boolean name' => [true, false],
];
}
Have in mind I used a lot of things in here:
I have tested if the normal insertion works, if it is checking the a valid name is input (FormRequest rules) and that if the user is not logged in it should throw an unauthorized exception.
I have used #depends, that is used to run tests ONLY if the dependant test passes, that way we can prevent running the "negative" tests just because the normal flow did not succeed, so it makes no sense to run the other ones and also get a "the test did not pass".
I have also used #dataProvider, that is used to share data with a test, so instead of copy-pasting the test a lot of times with data variation, you just vary what data you want the test to use.

Where do I put this validation code if its not coming from a request?

Consider this test:
public function a_slot_cant_have_the_same_start_and_end_time(){
$this->expectException(Exception::class);
factory(Slot::class)->create(['from'=>now(),'to'=>now()]);
}
I need to test that the content of the from and to attributes are not the same. This isn't coming through a request though so I can't use the normal laravel validation (I dont think).
I've resorted to overloading the boot method on the model and using model events , but that feels hacky.
You can put them anywhere you want:
...
use Validator;
...
$rules = [
'field_one' => 'nullable|string|max:255',
'field_two' => 'nullable|string|max:255',
];
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
// Do what you want
}

How to check data exists in the database

I have a function to add new property. But i want to check for duplicate data at column "code" before add new data into database. If data exists will appear a message error.
function addPro(Request $req)
{
$id = $req->type_id;
$type = AssetType::find($id);
if($req->save == 'save'){
$pro = new TypeProperties;
$pro->name = $req->name;
$pro->code = $req->code;
$pro->type = $req->type;
$pro->assettype_id = $req->type_id;
$pro->save();
Schema::table($type->code, function ($table) use ($pro) {
if ($pro->type == "textbox")
$table->string($pro->code )->nullable();
if ($pro->type == "textarea")
$table->text($pro->code )->nullable();
});
return redirect(url($type->id.'/add/property'))->with('message','Save successful');
}
return redirect(url('asset/type/'.$type->id));
}
You can use laravel Request Validation
function addPro(Request $req)
{
$id = $req->type_id;
$type = AssetType::find($id);
if($req->save == 'save'){
$req->validate([
'code' => 'required|unique:tablename'
]);
$pro = new TypeProperties;
$pro->name = $req->name;
$pro->code = $req->code;
$pro->type = $req->type;
$pro->assettype_id = $req->type_id;
$pro->save();
Schema::table($type->code, function ($table) use ($pro) {
if ($pro->type == "textbox")
$table->string($pro->code )->nullable();
if ($pro->type == "textarea")
$table->text($pro->code )->nullable();
});
return redirect(url($type->id.'/add/property'))->with('message','Save successful');
}
return redirect(url('asset/type/'.$type->id));
}
The most simple way to do this is by checking if code is_null :
if (is_null($pro->code)) {
// It does not exist
} else {
// It exists
}
The other way is to make a validation using Laravel's built in ValidateRequest class. The most simple use-case for this validation, is to call it directly in your store() method like this:
$this->validate($req, [
'code' => 'required|unique,
//... and so on
], $this->messages);
With this, you're validating users $req by saying that specified columns are required and that they need to be unique, in order for validation to pass. In your controller, you can also create messages function to display error messages, if the condition isn't met:
private $messages = [
'code.required' => 'Code is required',
'code.unique' => 'Code already exists',
//... and so on
];
You can also achieve this by creating a new custom validation class:
php artisan make:request StorePro
The generated class will be placed in the app/Http/Requests directory. Now, you can add a few validation rules to the rules method:
public function rules()
{
return [
'code' => 'required|unique,
//... and so on
];
}
All you need to do now is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:
public function store(StorePro $req)
{
// The incoming request is valid...
// Retrieve the validated input data...
$validated = $req->validated();
}
If you have any additional question about this, feel free to ask. Source: Laravel official documentation.
What does your migration look like for AssetType?
I ask because you can do this in the schema with ->unique() added to the column on the creation or make a migration to add the constraint.
You can also check with something like this:
// Search database table for entry
$entry = AssetType::where('code', '=', $pro->code)->first();
// If not found
if ($entry === null) {
// Save method here.
}
Otherwise, you can use the manual validator or create a Request with validation
References:
https://laravel.com/docs/5.8/queries#where-clauses
https://laravel.com/docs/5.8/validation#creating-form-requests
https://laravel.com/docs/5.8/validation#manually-creating-validators

What are the benefits of using Laravel Request Class in API's development?

Is there any benefit of using laravel requests classes for store and update methods in developing restful API's? Or do I have to make custom Validator::make response?
I have been facing difficulty in modifying the response format from failed requests as to follow some development standards requirements.
Can we modify the failed responses format from request class for API's?
I prefer to use independent from request class because there is at least one benefit: more clear code.
you can generate response as you wish like this (this is my solution, maybe there are more better solutions. i use this solution to return only one validation error not all. you can modify it as you wish):
in your Form request class add this method:
protected function failedValidation(Validator $validator)
{
$this->validator = $validator;
foreach ($validator->messages()->getMessages() as $key => $value) {
$first_messages_only[$key] = $value[0];
}
throw new ValidationException($first_messages_only);
}
and then in your Exception handler class, write this block of code in your render() method:
if ($exception instanceof ValidationException) {
$response = [
'status' => false,
'message' => trans('api.general.validation_not_passed'), // $exception->getMessage()
'data' => ['validation_errors' => $exception->validator]
];
return response()->json($response);
}
Since you asked its usage in API development then you can easily tell request class that you want json response by adding application/json header in your request then it will return json response.
Request class is best approach to validate incoming input from user which provides a lot of other features as well.
In Request class you can write validation rules for all request types e.g. get,post,put|patch or delete
You can allow or disallow anyone using authorize method based on your project logic.
You can write custom messages and send them custom error message bags.
If you write whole thing in a controller method then that will not be a good approach and difficult to manage while request class will make you comfortable while dealing with validations only.
protected $errorBag = 'custom_errors_bag'
public function authorize()
{
return true; //or any other logic here to authorize the user
}
public function rules()
{
switch ($this->method()){
case 'POST':
return [
'username' => 'required|string|max:20|unique:users',
//...
];
case 'PUT':
case 'PATCH':
return [
'username' => 'required|string|max:20|unique:users,id',
//...
];
case 'DELETE':[
'id' => 'required'
//...
];
default:break;
}
}
public function messages()
{
return [
'username.required' => 'Please provide username',
'username.unique' => 'Username must be unique',
//...
];
}

Creating edit function in the same controller laravel

So I have a create function in my controller as shown below and my routes is as such, my question is is there a way for me to put a condition to different create and edit in the same function as both have quite similar coding. Can someone enlighten me pls?
class ManageAccountsController extends Controller
{
public function index() {
$users = User::orderBy('name')->get();
$roles = Role::all();
return view('manage_accounts', compact('users', 'roles'));
}
public function update()
{
// process the form here
// create the validation rules ------------------------
$rules = array(
'name' => 'required', // just a normal required validation
'email' => 'required|email|unique:users', // required and must be unique in the user table
'password' => 'required|min:8|alpha_num',
'password_confirm' => 'required|same:password', // required and has to match the password field
'mobile' => 'required',
'role_id' => 'required'
);
// do the validation ----------------------------------
// validate against the inputs from our form
$validator = Validator::make(Input::all(), $rules);
// check if the validator failed -----------------------
if ($validator->fails()) {
// redirect our user back to the form with the errors from the validator
$input = Input::except('password', 'password_confirm');
$input['autoOpenModal'] = 'true'; //Add the auto open indicator flag as an input.
return redirect()
->back()
->withInput($input)
->withErrors($validator);
} else {
// validation successful ---------------------------
// user has passed all tests!
// let user enter the database
// create the data for our user
$user = new User;
$user->name = Input::get('name');
$user->email = Input::get('email');
$user->password = Hash::make(Input::get('password'));
$user->mobile = Input::get('mobile');
$user->role_id = Input::get('role_id');
// save our user
$user->save();
// redirect ----------------------------------------
// redirect our user back to the form so they can do it all over again
Session::flash('flash_message', 'User successfully added!');
return redirect()->back();
}
}
}
routes.php
Route::get('manage_accounts', 'ManageAccountsController#index');
Route::post('manage_accounts', 'ManageAccountsController#update');
UPDATE OR CREATE
Try the updateOrCreate() in Eloquent to create or update a record matching the attributes.
Read API docs udateOrCreate()
Your code will be like:
Model::updateOrCreate( ['id' => $id], ['firstField' => 'value', 'secondField' => 'value'] );
Note: first parameter is the match to be found and second the data's to be saved.
Hope this is helpful.
Why don't you try moving some of this code out of your controller. If you were to use Repositories, then you would be able to encapsulate some of your logic in order to use it for both functions.
Also you can handle all this validation without writing all the extra code into your controller - see http://laravel.com/docs/5.0/validation#form-request-validation.
This may all seem a bit overkill at first, but once you get the hang of it, your code will be much more manageable and extendable.
(for more on these I would thoroughly recommend Jeffery Way's Laracasts https://laracasts.com/ - this helped me a lot when I was learning Laravel)
// routes.php
// http://laravel.com/docs/5.0/controllers#restful-resource-controllers
Route::resource('manage_accounts', 'ManageAccountsController');
// ManageAccountsController.php
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
class ManageAccountsController extends Controller
{
public $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function index() {
$users = User::orderBy('name')->get();
$roles = Role::all();
return view('manage_accounts', compact('users', 'roles'));
}
public function store(StoreUserRequest $request)
{
// validation already handled using this: http://laravel.com/docs/5.0/validation#form-request-validation
$this->userRepository->upsert($request)
Session::flash('flash_message', 'User successfully added!');
return redirect()->back();
}
public function update(StoreUserRequest $request, $id)
{
// validation already handled using this: http://laravel.com/docs/5.0/validation#form-request-validation
$this->userRepository->upsert($request, $id)
Session::flash('flash_message', 'User successfully updated!');
return redirect()->back();
}
}
// UserRepository.php
class UserRepository {
public function upsert($data, $id = null)
{
// You will also need something like this
if(isset($data['id']))
{
$user = $this->user->find($data['id']);
}
else {
$user = new User;
}
$user->name = $data['name'];
$user->email = $data['email'];
$user->password = Hash::make($data['password']);
$user->mobile = $data['mobile'];
$user->role_id = $data['role_id'];
// save our user
$user->save();
return $user;
}
}
}
Please use the code here as a guide (I have written this in a hurry and it will certainly contain errors). Have a quick read up on repositories and I think it should all make sense.
The basic premise here is to separate out code that you want to re-use rather than squashing it all into the same function.
Hope this helps!

Resources