How to use Laravel Input::replace() in testing POST requests - php

I'm having some trouble using Laravel's Input::replace() method to simulate a POST request during unit testing.
According to Jeffrey Way here and here, you can do something like this:
# app/tests/controllers/PostsControllerTest.php
public function testStore()
{
Input::replace($input = ['title' => 'My Title']);</p>
$this->mock
->shouldReceive('create')
->once()
->with($input);
$this->app->instance('Post', $this->mock);
$this->call('POST', 'posts');
$this->assertRedirectedToRoute('posts.index');
}
However, I can't get this to work. Input::all() and all Input::get() calls still return an empty array or null after Input::replace() is used.
This is my test function:
public function test_invalid_login()
{
// Make login attempt with invalid credentials
Input::replace($input = [
'email' => 'bad#email.com',
'password' => 'badpassword',
'remember' => true
]);
$this->mock->shouldReceive('logAttempt')
->once()
->with($input)
->andReturn(false);
$this->action('POST', 'SessionsController#postLogin');
// Should redirect back to login form with old input
$this->assertHasOldInput();
$this->assertRedirectedToAction('SessionsController#getLogin');
}
The $this->mock->shouldReceive() doesn't get called with $input though - it only gets an empty array. I've confirmed this in the debugger by looking at Input::all() and Input::get() for each value, and they're all empty.
TL/DR: How do I send a request with POST data in a Laravel unit test?

You should use Request::replace(), not Input::replace in order to replace input data for the current request.

Related

Call to a member function fails() on array

I have a problem with the laravel validation.
Call to a member function fails() on array
Symfony\Component\Debug\Exception\FatalThrowableError thrown with message "Call to a member function fails() on array"
Stacktrace:
`#0 Symfony\Component\Debug\Exception\FatalThrowableError in
C:\laragon\www\frontine\app\Http\Controllers\authController.php:37
public function postRegister(Request $request)
{
$query = $this->validate($request, [
'user' => 'string|required|unique:users|min:4|max:24',
'email' => 'email|string|required|unique:users',
'pass' => 'string|required|min:8',
'cpass' => 'string|required|min:8|same:pass',
'avatar' => 'image|mimes:jpeg,jpg,png|max:2048',
]);
if ($query->fails())
{
return redirect('/registrar')
->withErrors($query)
->withInput();
}
}
The error is because what the ->validate() method returns an array with the validated data when applied on the Request class. You, on the other hand, are using the ->fails() method, that is used when creating validators manually.
From the documentation:
Manually Creating Validators
If you do not want to use the validate method on the request, you may
create a validator instance manually using the Validator facade. The
make method on the facade generates a new validator instance:
use Validator; // <------
use Illuminate\Http\Request;
class PostController extends Controller
{
public function store(Request $request)
{
$validator = Validator::make($request->all(), [ // <---
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
if ($validator->fails()) {
return redirect('post/create')
->withErrors($validator)
->withInput();
}
// Store the blog post...
}
}
The ->fails() is called in the response of the Validator::make([...]) method that return a Validator instance. This class has the fails() method to be used when you try to handled the error response manually.
On the other hand, if you use the validate() method on the $request object the result will be an array containing the validated data in case the validation passes, or it will handle the error and add the error details to your response to be displayed in your view for example:
public function store(Request $request)
{
$validatedData = $request->validate([
'attribute' => 'your|rules',
]);
// I passed!
}
Laravel will handled the validation error automatically:
As you can see, we pass the desired validation rules into the validate
method. Again, if the validation fails, the proper response will
automatically be generated. If the validation passes, our controller
will continue executing normally.
What this error is telling you is that by doing $query->fails you're calling a method fails() on something (i.e. $query) that's not an object, but an array. As stated in the documentation $this->validate() returns an array of errors.
To me it looks like you've mixed a bit of the example code on validation hooks into your code.
If the validation rules pass, your code will keep executing normally;
however, if validation fails, an exception will be thrown and the
proper error response will automatically be sent back to the user. In
the case of a traditional HTTP request, a redirect response will be
generated, [...]
-Laravel Docs
The following code should do the trick. You then only have to display the errors in your view. You can read all about that, you guessed it, in... the docs.
public function postRegister(Request $request)
{
$query = $request->validate($request, [
'user' => 'string|required|unique:users|min:4|max:24',
'email' => 'email|string|required|unique:users',
'pass' => 'string|required|min:8',
'cpass' => 'string|required|min:8|same:pass',
'avatar' => 'image|mimes:jpeg,jpg,png|max:2048',
]);
}

How to check for required fields in object?

i have example object with fields
name => John
surname => Dow
job => engineer
and output form with placeholders. some required, some not.
what is best practice for check if it requred and show error with null fields?
There are multiple ways actually you can do that inside of controller method or make use of Laravels Request Classes for me I prefer to use Request Classes
look below I will list the two examples
Validate inside the controller's method
public function test(Request $request){
if($request->filled('name){
/*filled will check that name is set on the current
request and not empty*/
//Do your logic here
}
}
Second way is by using the Validator Facade inside your controller
use Validator;
class TestController{
public function test(Request $request){
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
/*continue with your logic here if the request failed on
validator test Laravel will automatically redirect back
with errors*/
}
}
Third way my favorite one personally
you can generate a Request class using this command
php artisan make:request AddBookRequest
that will generate the request class under "app/Http/Requests/AddBookRequest" , inside of any generated request class you will find two methods authorize() and rules()
in the authorized method you have to return truthy or falsy value this will detect if the current user making the request has authorization to fire this request inside of the rules method you do pretty much as you did in the Validator in the second way check the example
public function authorize(){
return true;
}
public function rules(){
return [
'title' => 'required|string',
'author_id' => 'required|integer'
];
}
then simply in your controller you can use the generated request like this
use App\Http\Requests\AddBookRequest;
public function store(AddBookRequest $request){
/* do your logic here since we uses a request class if it fails
then redirect back with errors will be automatically returned*/
}
Hope this helps you can read more about validation at
https://laravel.com/docs/5.6/validation
I think "simple is the best", just through object and check if properties exists
Ref: property_exists
Example:
if (property_exists($object, 'name')) {
//...do something for exists property
} else {
//...else
}

Testing message passed to view

How can I test that I get this message:
public function doSomething()
{
if($ok){
return view('message-page')
->with('title','The message?')
}
}
What can I assert to check the message that is passed to the view?
Updated:
Laravel 5.5
I think it is tricky because I am not doing a HTTP call, which would return a response. I am just doing a function call ($foo->doSomething();), so I don't get a response returned.
I can't do a GET because I need to pass in a mocked object. Here is my test so far:
public function do_the_test()
{
//Arrange
$mockObject = Mockery::mock('App\MockObject');
$authCode = 123456;
$mockObject->shouldReceive('doSomething')->once()->with($authCode)->andReturn([
'code' => '123456',
'name' => 'joe',
'username' => 'smith',
'email' => 'joe#yahoo.co.uk',
'domain' => 'yahoo.co.uk'
]);
//Act
$object = new Foo($mockObject);
$object->doSomething();
//Assert
??
//check that view is returned with message text
}
Progress:
I have hacked this by setting a session variable (instead of passing the messages with the view) and then checking that with assertEquals();
Would be nice to find a better way.
Not sure which laravel version you have, but still, you can use assertViewHas($key, $value) function:
public function testViewDoSomethingHasCorrectTitle()
{
$response = $this->call('GET', '/my_route');
$this->assertViewHas('title', 'The message?')
}
https://laravel.com/docs/5.5/http-tests#assert-view-has
just do a dd() and you'll know if it passed to the controller.
In your view do a {{ dd($title) }}

How to remove an existing item from a route request parameters? - Laravel 5.5

I want to remove an existing item from a request parameters that passes in a controller.
Here's my controller:
public function getIndex(Request $request)
{
// I need to remove a parameter from the $request here.
}
Actually, I want to dispatch a request in a controller but when I make a new instance of Request like this,
$new_request = new Request();
and add some fields to the $new_request like this:
$request->request->add([
'id' => '2',
'name' => 'test'
]);
Nothing is added! and the dispatch method can not yield a correct response with empty request!
But when I use an existing route request, every thing is ok, except extra items and I should get rid of them!
Do you need this?
public function getIndex(Request $request)
{
$request->request->remove('yourParamName');
}

How can I declare a route and a controller method that explicitly expect two query parameters like this?

I am pretty new in Laravel and I have the following problem.
I have to declare a route that handle requests like this:
http://laravel.dev/activate?email=myemail#gmail.com&token=eb0d89ba7a277621d7f1adf4c7803ebc
So this URL have to mantein this format (activate as resource and 2 query parameters in the form ?email and &token. This is not a REST URI. I need to use it in this way).
I need a route and a controller method that handle this kind of request.
I know that I can do something like this:
1) Into the routes/web.php file I declare a rout like this
Route::get('/activate','MyController#myMethod');
2) Into MyController I declare this method:
public function myMethod(Request $request) {
if(Input::has('email') && Input::has('token')) {
$email = $request->input('email');
$token= $request->input('token');
// DO SOMETHING USING $email AND $token
}
else {
// SHOW ERROR PAGE
}
}
But onestly I find it pretty horrible. I came from Java and using Spring framework I can specify in the mapping between the URL and the controller method that a method handle a request toward a resource and that to be handled some specific query paramer have to be passed. Otherwise (if these parameter are not present the HTTP GET request is not handled by the controller method).
I know that in Laravel I can do something like this using REST form of my URL, I think something like this:
For the route mapping:
Route::get('/activate/{email}/{token}', [ 'uses' => 'ActivationController#activate', 'as' => 'activate' ]);
And then the controller method will be something like this:
public function activate($email, $token) {
// $email would be 'myemail#gmail.com'
// $token would be 'eb0d89ba7a277621d7f1adf4c7803ebc'
// do stuff...
}
But I can't use this kind of URI in this case (as specified the URL pattern have to be in the format:
http://laravel.dev/activate?email=myemail#gmail.com&token=eb0d89ba7a277621d7f1adf4c7803ebc
So I desire do something like this with this kind of URI. I don't wont to pass the Request $request object to my controller method and then extract the parameters from it but I want to have a controller method signature like this:
public function activate($email, $token) {
in which if all the expected parameters are not passed simply the request will not handled by this controller.
I prefer do it because for me is a neater approach (reading the controller signature another developer immediately know that this method is expecting).
And also because I want that malformed parameters are not handled by the method (so I can avoid to handle error cases like: the user not pass the 2 parameters).
How can I do something like this in Laravel? (if it is possible...)
This is certainly a strange request, because it defies a more traditional use of Laravel routing practices, but this should work for you:
In routes.php (Laravel < 5.3) or web.php (Laravel 5.4+):
Route::get('/activate', [ 'as' => 'activate', function()
{
return app()->make(App\Http\Controllers\ActivateController::class)->callAction('activate', $parameters = [ 'email' => request()->email, 'token' => request()->token ]);
}]);
So we are instantiating the ActivateController class and calling the method 'activate' which is the first argument, then supplying a list of parameters the method receives in the form of an array.
public function activate($email, $token)
{
echo "Email: $email"; // myemail#gmail.com
echo "Token: $token"; // eb0d89ba7a277621d7f1adf4c7803ebc
// do stuff
}
Now, provided you go to http://laravel.dev/activate?email=myemail#gmail.com&token=eb0d89ba7a277621d7f1adf4c7803ebc $email and $token will be the respective key query parameters.
Now, as for validating to ensure that data is present. You have to add another argument to your activate() method, unless you want to do it inline.
The best practice way:
Run php artisan make:request ActivateRequest
A new file called ActivateRequest will be created in app\Http\Requests. Open it up and make sure your authorize() method returns true like so:
public function authorize()
{
return true;
}
Next, in the rules() method there is an array. Add the following:
return [
'email' => 'required|email',
'token' => 'required',
];
If you want to supply your own validation messages to these rules, add a method below rules() called messages() Like so:
public function messages()
{
return [
'email.required' => 'You must supply an email address.',
'email.email' => 'That email is address is not valid.',
'token.required' => 'Please supply a token.',
];
}
Then, back in ActivateController pull in your new FormRequest class at the top use App\Http\Requests\ActivateRequest; and use it in the method like so:
activate($email, $token, ActivateRequest $request) {
...
Now, your request will be validated and you don't even have to use the $request variable.
The inline (not so best practice way)
If you are dead set on not having an extra argument in your method, you can validate data in the controller like so:
First, bring in the Validator class at the top. use Validator;.
public function activate($email, $token)
{
$validationMessages = [
'email.required' => 'You must supply an email address.',
'email.email' => 'That email is address is not valid.',
'token.required' => 'Please supply a token.',
];
$validation = Validator::make(request()->toArray(), [
'email.required' => 'You must supply an email address.',
'email.email' => 'That email is address is not valid.',
'token.required' => 'Please supply a token.',
], $validationMessages);
if ( $validation->fails() )
{
return 'Sorry, there was a problem with your request';
}
// do stuff
}
Hope this helps.

Categories