How to PHPUnit form validation on lumen?
This is because I am receiving the following error.
BadMethodCallException: Method [validateTest] does not exist.
C:\work\test\vendor\illuminate\validation\Validator.php:3360
C:\work\test\vendor\illuminate\validation\Validator.php:517
C:\work\test\vendor\illuminate\validation\Validator.php:517
C:\work\test\vendor\illuminate\validation\Validator.php:431
C:\work\test\vendor\illuminate\validation\Validator.php:456
C:\work\test\vendor\laravel\lumen-framework\src\Routing\ProvidesConvenienceMethods.php:63
C:\work\test\app\Http\Controllers\BusinessInfoController.php:30
C:\work\test\tests\app\Http\Controllers\BusinessInfoControllerTest.php:17
C:\Users\chew\AppData\Roaming\Composer\vendor\phpunit\phpunit\src\TextUI\Command.php:188
C:\Users\chew\AppData\Roaming\Composer\vendor\phpunit\phpunit\src\TextUI\Command.php:118
On my controller, it error out on this line.
public function getUsers(Request $request, InfoRequest $infoRequest)
{
$this->validate($request, $infoRequest->ruleGetInfo());
....
}
While on the InfoRequest:
public function ruleGetInfo()
{
return [
'email' => 'required',
'password' => 'required'
];
}
I'm not sure why is it looking for a validateTest method. I even tried adding it on my phpunit test file and the actual controller file itself (just to test things out) but it still gives the same error.
Related
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',
]);
}
I'm trying to write some tests for my forms in order to confirm the validators retrieve the expected errors when required.
The form only has 3 fields: name, discount and expiration and the validator looks like this:
$this->validate($request, [
'name' => 'required',
'discount' => 'required|numeric|between:1,100',
'expiration' => 'required|date_format:d/m/Y',
]);
That works fine both when submitting the form and when running the tests with phpunit using the following code:
/**
* Discount must be numeric check
*/
$response = $this->post(route('offer.create'), [
'name' => $faker->sentence(4),
'discount' => 'asdasd',
'expiration' => $faker->dateTimeBetween('+1 days', '+5 months')
]);
// Check errors returned
$response->assertSessionHasErrors(['discount']);
Since discount is not numeric it throws the expected error and everybody is happy.
Now, if I want to add a new rule to make sure that the expiration is equal or greater to today I add the after:yesterdayrule leaving the validator like:
$this->validate($request, [
'name' => 'required',
'discount' => 'required|numeric|between:1,100',
'expiration' => 'required|date_format:d/m/Y|after:yesterday',
]);
That works fine when submitting the form. I get the error saying the discount is not numeric, but when testing with phpunit it doesn't get the error as expected:
1) Tests\Feature\CreateSpecialOfferTest::testCreateSpecialOffer
Session missing error: expiration
Failed asserting that false is true.
Why adding this new validation rule to expirationgenerates a false validation in discount? Is this a bug in the validator or am I missing something?
Also:
1 - is there a better way to test form validators?
2 - is there an assert that is the opposite of assertSessionHasErrors() to check a certain error is NOT been thrown?
If you see this kind of errors in PHPUnit: Failed asserting that false is true., you can add the 'disableExceptionHandling' function to tests/TestCase.php:
<?php
namespace Tests;
use Exception;
use App\Exceptions\Handler;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
protected function disableExceptionHandling()
{
// Disable Laravel's default exception handling
// and allow exceptions to bubble up the stack
$this->app->instance(ExceptionHandler::class, new class extends Handler {
public function __construct() {}
public function report(Exception $exception) {}
public function render($request, Exception $exception)
{
throw $exception;
}
});
}
}
In your test you call it like this:
<?php
/** #test */
public function your_test_function()
{
$this->disableExceptionHandling();
}
Now, the full output of the error and stacktrace will be shown in the PHPUnit console.
I would like to make a validator through the usage of Laravel requests, and validation is working fine. But if I don't do them in the controller I can't return back to view with errors if validator fails.
Is it possible to implement this:
if ($validator->fails()) {
return redirect('post/create')
->withErrors($validator)
->withInput();
}
within the custom request? Something like this maybe:
public function rules()
{
return [
'name' => 'required',
'email' => 'required|email|unique:users',
'password' => 'required|min:6|confirmed',
]; // ->withErrors()
}
FormRequest by default returns the user back to the previous page with the errors and input. You don't have to specify it manually. Just set the rules and use the newly created FormRequest in your controller method instead of using the Request object.
This is what happens under the hood.
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors, $this->errorBag);
I've been struggling with this for a while now.
Here's the code I've got.
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|max:100'
]);
if ($validator->fails()) {
//do something
}
}
The problem is that I get a FatalThrowableError right in my face with the following message:
Call to a member function parameter() on array
I can't find what I'm doing wrong. I'd appreciate some help here.
And also, I've had this validation before which worked:
$this->validate($request, [
'name' => 'required|unique:developers|max:100'
]);
But the thing with this one is, I had no idea how to catch when the validation failed. Is it possible to catch the validation fail when using it this way?
Using version: "laravel/lumen-framework": "5.2.*"
A FatalThrowableError exception is low level exception that is thrown typically by the symfony debug ErrorHandler. In lumen the queue worker, PhpEngine, console kernel and routing pipeline uses it as well.
Make sure of the following
That you have copied .env.example to .env
If you are using Facades, make sure that you enabled it inside bootstrap/app.php by uncommenting the line.
$app->withFacades();
Inside Lumen 5.2.8 either of the following would work.
The following will actually return a valid JSON object with the errors. You did not elaborate on your use case why that is not sufficient. What is nice with using the validate call like this is it actually returns a 422 http status code, which implies an unprocessed entity.
$app->get('/', function (Request $request) {
$this->validate($request, [
'name' => 'required'
]);
});
Using the facade works as well, albeit is returns a 200 status code.
$app->get('/', function (Request $request) {
$validator = Validator::make($request->only(['name']), [
'name' => 'required'
]);
if ($validator->fails()) {
return ['error' => 'Something went wrong'];
}
});
If you still do not come right with the Validator::make you can catch the default Validation exception using. It feels a little hacky.
$app->get('/', function (Request $request) {
try {
$this->validate($request, [
'name' => 'required'
]);
} catch (\Illuminate\Validation\ValidationException $e) {
// do whatever else you need todo for your use case
return ['error' => 'We caught the exception'];
}
});
I built an API using dingo/api 0.10.0, Laravel 5.1 and lucadegasperi/oauth2-server-laravel": "^5.1".
All my routes work fine in Postman/Paw!
The problem appears when I try to test the API using PHPUnit.
This is part of my route-api.php file
<?php
$api = app('Dingo\Api\Routing\Router');
$api->version(['v1'], function ($api) {
$api->post('oauth/access_token', function () {
return response(
\LucaDegasperi\OAuth2Server\Facades\Authorizer::issueAccessToken()
)->header('Content-Type', 'application/json');
});
$api->group(['middleware' => ['oauth', 'api.auth']], function ($api) {
$api->post('/register', 'YPS\Http\Controllers\Api\UserController#register');
});
And this is my test file UserRegistrationTest.php
class UserRegistrationTest extends ApiTestCase
{
public function setUp()
{
parent::setUp();
parent::afterApplicationCreated();
}
public function testRegisterSuccess()
{
$data = factory(YPS\User::class)->make()->toArray();
$data['password'] = 'password123';
$this->post('api/register', $data, $this->headers)
->seeStatusCode(201)
->seeJson([
'email' => $data['email'],
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
]);
}
public function testRegisterMissingParams()
{
$this->post('api/register', [], $this->headers, $this->headers, $this->headers)->seeStatusCode(422);
}
}
The ApiTestCase simply retrieves a token and sets the headers.
private function setHeaders()
{
$this->headers = [
'Accept' => 'application/vnd.yps.v1+json',
'Authorization' => 'Bearer ' . $this->OAuthAccessToken,
];
}
Now, the weird part is that the first test testRegisterSuccess runs perfectly and returns the response I expect. But the second one testRegisterMissingParams, even though it's the same route, returns this,
array:2 [
"message" => "The version given was unknown or has no registered routes."
"status_code" => 400
]
I tracked the error and it is in the Laravel adapter here:
public function dispatch(Request $request, $version)
{
// it seems that the second time around can't find any routes with the key 'v1'
if (! isset($this->routes[$version])) {
throw new UnknownVersionException;
}
$routes = $this->mergeExistingRoutes($this->routes[$version]);
$this->router->setRoutes($routes);
return $this->router->dispatch($request);
}
And further more, if i run one test at a time (eg comment one out, run test and then comment the other and run test) i see the result expected in both tests. The problem is when i run multiple tests.
Any thoughts on that?
Thank you!
Run php artisan api:routes to see full path you may have missed something for the URL, also if this working if you request your URL manually?
I had same problem with testing using Dingo & Lumen. This worked for me - remove bootstrap="bootstrap/app.php" from phpunit.xml file and change line processIsolation="false" to processIsolation="true".