Laravel Testing - How to validate each fields in JSON response - php

I want to test my endpoint with valid format. For example, I have /api/token (POST) which will return api token.
In my case, this endpoint will return "token" string and "message" fields. Thus, I want to check if this two fields are exist with valid format. Currently I am using Laravel Validator.
Example json output:
{
"message": "login successful",
"token": "d4zmendnd69u6h..."
}
Test class (ApiTokenTest.php).
class ApiTokenTest extends TestCase
{
protected $validFormBody = [
'os_type' => 'android',
'device_id' => '0000-AAAA-CCCC-XXXX',
'os_version' => '5.1',
'apps_version' => '1.0',
];
public function testSucessResponseFormat()
{
$response = $this->json('post', '/api/token', $this->validFormBody);
$validator = Validator::make(json_decode($response->getContent(), true), [
'token' => 'required|size:100', // token length should be 100 chars
'message' => 'required',
]);
if ($validator->fails()) {
$this->assertTrue(false);
}
else {
$this->assertTrue(true);
}
}
}
The issue here is the failure message is not really helps, especially if I have more than 1 fields that are not in valid format, should i assert one-by-one?. (see below phpunit output on failure case). What should I use in order to validate format for each fields?
Thanks in advance.
There was 1 failure:
1) Tests\Feature\ApiTokenTest::testSucessResponseFormat
Failed asserting that false is true.

It looks like you are not displaying anything in the terminal if there is in fact any validation errors, what you can do is a var_dump into the terminal if the validator fails, like so:
if ($validator->fails()) {
var_dump($validator->errors()->all());
$this->assertTrue(false);
}
else {
$this->assertTrue(true);
}

Related

Laravel API not returning validation messages properly

I am using Laravel as an API base, but I am struggling to show meaningful validator messages in my JSON response. I am using Laravel 8.
This is the response when my validator fails:
{
"message": "The given data was invalid.",
"errors": {
"image": [
"validation.mimes"
]
}
}
But instead of the validation.mimes i want an actual meaningful message, like The :attribute must be a file of type: :values..
I've tried overriding the exception in Exceptions\Handler.php but I still can't seem to access actual error messages?
protected function invalidJson($request, ValidationException $exception) {
return response()->json([
'success' => false,
'message' => $exception->getMessage(),
'errors' => $exception->errors()
], $exception->status);
}
Any help is appreciated.
Thanks

Laravel Custom Wrapper for API Responses

I am trying to structure my project in Laravel just created to use it as a back-end API. I want all my responses from Laravel to be returned in the JSON:API format defined on the official site: https://jsonapi.org/format/
For example:
I have created the following 2 resource files:
1- php artisan make:resource User
2- php artisan make:resource UserCollection --collection
Two simple resource files, one to return a resource and one to return a collection.
Now, I would like to return in all my responses the following format:
In case of success
1- The status return code can be 200, 201 or 202.
2- The returned response should be similar to the following:
{
"data": [
{
"id": 1,
"email": "collins.elody#example.org"
}
],
"message": null,
"success": true
}
You may be wondering what is the point of passing the message key, in this case it is null because it would be returning a collection of records, that is, reading, but in case you needed to add a new record, update or delete one, you would need to pass a message to my front-end, in this case I would use that key for that.
Example, adding record, response status code 201:
{
"data": [],
"message": "Record created succesfully",
"success": true
}
In case of failure
As said here: https://jsonapi.org/format/#document-top-level : The members data and errors MUST NOT coexist in the same document.
So, in case of error, I need change data key by errors key, for example, suppose I am trying to authenticate myself, and the validation fails, in this case, it should turn out like this:
{
"errors": {
"email": "E-Mail is required",
"password": "Password is required"
},
"message": null,
"success": false
}
or I just want to return an error message, expected output should by:
{
"errors": [],
"message": "Something is Wrong!",
"success": false
}
So in essence what I need is a global wrapper for all the responses I make from Laravel. I would like to call return in an elegant way as follows:
return $this->success($collection);
or
return $this->success('Done!', 201);
So the first thing that came to mind was creating a trait and defining the methods you need to then call them from anywhere in Laravel
My Trait
<?php
namespace App\Traits;
trait APIResponse
{
public function success($data, $status = 200) {
return [
'data' => $data,
'success' => in_array($status, [200, 201, 202]) ? true : false,
'message' => null
];
}
public function failure($data, $status = 500) {
// TODO
}
}
My Controller
class ExampleController extends Controller
{
public function index() {
$collection = new UserCollection(User::all());
return $this->success($collection);
}
}
But I am not sure it is the right way to do it, please, someone skilled in the field who can help me. Thank you very much in advance.
You are on the right path, there is two main solutions i would consider best approaches, to handling your exact problem. Fractal and Eloquent Resources, i prefer Fractal due to some design decisions and experience.
I will show an example in fractal, using the wrapper by Spatie. Firstly create an serializer that will wrap the data as expected.
class YourCustomSerializer extends SerializerAbstract
{
public function collection($resourceKey, array $data)
{
return [
$resourceKey ?: 'data' => $data,
'message': null,
'success': true,
];
}
public function item($resourceKey, array $data)
{
return [
$resourceKey ?: 'data' => $data,
'message': null,
'success': true,
];
}
This should be added to your fractal.php config, there is published through the spatie wrapper.
Transforming your data you need a transformer.
class UserTransformer extends TransformerAbstract
{
public function transform(User $user)
{
return [
'name' => $user->name,
'email' => $user->email,
];
}
}
Now you can transform your data into the expected format.
public function response($data, int $statusCode = Response::HTTP_OK)
{
return fractal($data, $this->transformer)->respond($statusCode);
}
For error codes, you should go to the Handler.php and add something similar to this. This is very naive way of doing it, but for know should get you going on error handling, you need to do something with validation exception, status code etc.
if ($request->wantsJson()) {
return response()->json([
'success' => false,
'message' => $exception->getMessage(),
], Response::HTTP_BAD_REQUEST);
}

Customize Laravel API validation responses

I'm building a REST API with Laravel and wondering if there is a way to customize the API responses when validating.
For example, I have a validation rule in a Laravel request, saying a specific field is required.
public function rules() {
return [
'title'=>'required|min:4|max:100',
];
}
So, for this validation I get the error message in Postman like this
{
"title": [
"Please enter Ad Title"
]
}
What I want is to customize that response like this..
{
"success": false,
"message": "Validation Error"
"title": [
"Please enter Ad Title"
]
}
So, that the error is more specific and clear.
So, how to achieve this ?
Thanks!
I got a solution for your REST-API Validation Laravel FormRequest Validation Response are change by just write a few lines of code.
enter code here
Please add this two-line into your App\Http\Requests\PostRequest.php
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
after that add this function in your file.
you can change $response variable into your specific manner.
protected function failedValidation(Validator $validator) {
$response = [
'status' => false,
'message' => $validator->errors()->first(),
'data' => $validator->errors()
];
throw new HttpResponseException(response()->json($response, 200));
}
Provide a custom function to the FormRequest class called messages and return an array of validation messages mapped using dot notation for specific messages on specific rules:
public function messages()
{
return [
'title.required' => 'Please enter an Ad title',
'title.min' => 'Your title must be at least 4 character'
]
}
Returning a success message is futile as if it fails a 422 error code will be thrown when performing an ajax request anyway.
As for the message property, you will receive that as part of the payload, wherein the actual validation errors will be contained in the object.
You can customize errors, check the documentation. also you can validate in this way
$validator = Validator::make($request->all(), [
'title'=>'required|min:4|max:100'
]);
if ($validator->fails()) {
// get first error message
$error = $validator->errors()->first();
// get all errors
$errors = $validator->errors()->all();
}
then add them to your response, for example
return response()->json([
"success" => false,
"message" => "Validation Error"
"title" => $error // or $errors
]);

Slim3/DRY - How to handle errors/exceptions correctly without duplicating code?

I'm working on a fairly large JSON API using Slim3. My controllers/actions are currently littered with the following:
return $response->withJson([
'status' => 'error',
'data' => null,
'message' => 'Username or password was incorrect'
]);
At certain points in the application anything can go wrong and the response needs to be appropriate. But one thing that is common is the error responses are always the same. The status is always error, the data is optional (in the case of form validation errors data will contain those) and message is set to indicate to the user or consumer of the API what went wrong.
I smell code duplication. How can I reduce the code duplication?
From the top of my head all I could think of doing was creating a custom exception, something like App\Exceptions\AppException that takes option data and the message will be obtained form $e->getMessage().
<?php
namespace App\Exceptions;
class AppException extends Exception
{
private $data;
public function __construct($message, $data = null, $code = 0, $previous = null)
{
$this->data = $data;
parent::__construct($message, $code, $previous);
}
public function getData()
{
return $this->data;
}
}
Following that create middleware that calls $next wrapped in a try/catch:
$app->add(function($request, $response, $next) {
try {
return $next($request, $response);
}
catch(\App\Exceptions\AppException $e)
{
$container->Logger->addCritical('Application Error: ' . $e->getMessage());
return $response->withJson([
'status' => 'error',
'data' => $e->getData(),
'message' => $e->getMessage()
]);
}
catch(\Exception $e)
{
$container->Logger->addCritical('Unhandled Exception: ' . $e->getMessage());
$container->SMSService->send(getenv('ADMIN_MOBILE'), "Shit has hit the fan! Run to your computer and check the error logs. Beep. Boop.");
return $response->withJson([
'status' => 'error',
'data' => null,
'message' => 'It is not possible to perform this action right now'
]);
}
});
Now all I have to do at points in the code is to throw new \App\Exceptions\AppException("Username or password incorrect", null).
My only issue with this is it feels like I'm using exceptions for the wrong reasons and it may make debugging a little more difficult.
Any suggestions on reducing the duplicates and cleaning up error responses?
You can achieve similar similar results by creating an error handler which outputs JSON.
namespace Slim\Handlers;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
final class ApiError extends \Slim\Handlers\Error
{
public function __invoke(Request $request, Response $response, \Exception $exception)
{
$status = $exception->getCode() ?: 500;
$data = [
"status" => "error",
"message" => $exception->getMessage(),
];
$body = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
return $response
->withStatus($status)
->withHeader("Content-type", "application/json")
->write($body);
}
}
You must also configure Slim to use your custom error handler.
$container = $app->getContainer();
$container["errorHandler"] = function ($container) {
return new Slim\Handlers\ApiError;
};
Check Slim API Skeleton for example implemention.

REST API in Laravel when validating the request

I'm currently trying out on how to build a RESTful API with Laravel and I'm currently in the process of creating a new user. This is just a test and I'm getting some result when trying to validate the request using validation in Laravel; here is the result:
I've been trying to create a new one by this code:
public function store()
{
$validation = Validator::make(Request::all(),[
'username' => 'required|unique:users, username',
'password' => 'required',
]);
if($validation->fails()){
} else{
$createUser = User::create([
'username' => Request::get('username'),
'password' => Hash::make(Request::get('password'))
]);
}
}
but then I don't know how to return the error in validation. But it keeps on giving me that HTML as showed in the image when I was trying to do the if with validation->fails(). Is there a way to get the validation in JSON format?
these code will help you, working for me.
$response = array('response' => '', 'success'=>false);
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
$response['response'] = $validator->messages();
} else {
//process the request
}
return $response;
You should probably return errors (which is an instance of Illuminate\Support\MessageBag) and encode that. A MessageBag instance allows you to convert it directly to its JSON representation.
$errors = $validation->errors();
return $errors->toJson();
Now not to toot my own horn but I've recently developed a RESTful API package for Laravel which does all of this for you and all you need to do is throw a simple exception. See my dingo/api package and the Wiki on returning errors. Basically, instead of returning the errors you would throw an exception.
throw new Dingo\Api\Exception\StoreResourceFailedException('Could not create a new user.', $validation->errors());
It would be represented by the following JSON.
{
"message": "Could not create a new user.",
"errors": {
"username": ["The username is already in use."]
}
}
Laravel provides out of the box a validation method that you can call from your Controller.
if you check the Laravel Controller abstract class you will find it uses a trait called ValidatesRequests
abstract class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
So you can use a method $this->validate(Request $request, array $rules); as you long as your controller class extends the Controller
the full method declaration is
public function validate(Request $request, array $rules, array $messages = [], array $customAttributes = [])
{
$validator = $this->getValidationFactory()->make($request->all(), $rules, $messages, $customAttributes);
if ($validator->fails()) {
$this->formatValidationErrors($validator);
}
}
If The $validator fails, the method will throw an error depending on the request type, if it is ajax (in this case you should include in the request headers (Accept application/json) it will return a JSON response containing the validation errors.
For laravel 5.5 and up, see docs: AJAX Requests & Validation
TL;DR: On failed validation a json response with a 422 is returned along with the validation error messages. It took me a bit of time to find those validation errors in the response object, so to see the error messages if you're using axios, try this in your browser console:
axios.post('/api/your-route-here')
.then(response => {
console.log(response.data);
}).catch(error => {
console.log(error.response.data.errors)
});
There are many ways to get a validator response first is to get an all validation error at the same time i.e you will get a response like below
$validator = \Validator::make($request->all(), [
'username' => 'required|unique:users, username',
'password' => 'required',
]);
if ($validator->fails()) {
$responseArr = CustomHelper::returnRespArr("");
$responseArr['message'] = $validator->errors();;
$responseArr['token'] = '';
return response()->json($responseArr, Response::HTTP_BAD_REQUEST);
}
Response you will get is:
{
"status": false,
"data": [],
"message": {
"username": [
"The username field is required."
],
"password": [
"The password field is required."
]
},
"is_valid": 0,
"token": ""
}
The second way to get a validation response. In this, you will get a one validator error a time.
if ($validator->fails()) {
$responseArr = CustomHelper::returnRespArr("");
$responseArr['message'] = $validator->messages()->first();;
$responseArr['token'] = '';
return response()->json($responseArr,Response::HTTP_BAD_REQUEST);
}
The response you will get
{
"status": false,
"data": [],
"message": "The username field is required.",
"is_valid": 0,
"token": ""
}
I am using Laravel 9.x and found a quite simple way to validate errors with REST APIs:
public function store(Request $request)
{
$input = $request->all();
$validator = Validator::make($input, [
'title' => 'required|string|max:50'
]);
// Will return an error, if validation fails.
// https://laravel.com/api/9.x/Illuminate/Foundation/Validation/ValidatesRequests.html#method_validateWith
$this->validateWith($validator, $request);
// Only use the properties that were validated.
$input = $validator->validated();
// Create a new event model, with the data provided.
$event = Event::create($input);
return new EventResource($event);
}
In order to return a json error message, make sure to set the Accept header of the client to application/json. I make the mistake to not set this in my debug client, so I only saw html/xml messages.
You can also force the output to json.

Categories