ValidationFailure tests

This commit is contained in:
icefox 2026-02-19 08:44:49 -03:00
parent 709201547c
commit f1d46dacb6
No known key found for this signature in database
7 changed files with 264 additions and 5 deletions

View file

@ -11,7 +11,8 @@
"pestphp/pest": "^4.4",
"phpstan/phpstan": "^2.1",
"friendsofphp/php-cs-fixer": "^3.94",
"orchestra/testbench": "^9.16"
"orchestra/testbench": "^9.16",
"pestphp/pest-plugin-laravel": "^4.0"
},
"license": "GPL-2.0-only",
"autoload": {

76
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1d9eef5574135e39ab7eaa6beae3fdad",
"content-hash": "336ed1e898bc39b0e9990becc327415c",
"packages": [
{
"name": "brick/math",
@ -7992,6 +7992,80 @@
],
"time": "2025-08-20T13:10:51+00:00"
},
{
"name": "pestphp/pest-plugin-laravel",
"version": "v4.0.0",
"source": {
"type": "git",
"url": "https://github.com/pestphp/pest-plugin-laravel.git",
"reference": "e12a07046b826a40b1c8632fd7b80d6b8d7b628e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pestphp/pest-plugin-laravel/zipball/e12a07046b826a40b1c8632fd7b80d6b8d7b628e",
"reference": "e12a07046b826a40b1c8632fd7b80d6b8d7b628e",
"shasum": ""
},
"require": {
"laravel/framework": "^11.45.2|^12.25.0",
"pestphp/pest": "^4.0.0",
"php": "^8.3.0"
},
"require-dev": {
"laravel/dusk": "^8.3.3",
"orchestra/testbench": "^9.13.0|^10.5.0",
"pestphp/pest-dev-tools": "^4.0.0"
},
"type": "library",
"extra": {
"pest": {
"plugins": [
"Pest\\Laravel\\Plugin"
]
},
"laravel": {
"providers": [
"Pest\\Laravel\\PestServiceProvider"
]
}
},
"autoload": {
"files": [
"src/Autoload.php"
],
"psr-4": {
"Pest\\Laravel\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "The Pest Laravel Plugin",
"keywords": [
"framework",
"laravel",
"pest",
"php",
"test",
"testing",
"unit"
],
"support": {
"source": "https://github.com/pestphp/pest-plugin-laravel/tree/v4.0.0"
},
"funding": [
{
"url": "https://www.paypal.com/paypalme/enunomaduro",
"type": "custom"
},
{
"url": "https://github.com/nunomaduro",
"type": "github"
}
],
"time": "2025-08-20T12:46:37+00:00"
},
{
"name": "pestphp/pest-plugin-mutate",
"version": "v4.0.1",

View file

@ -43,7 +43,7 @@ trait DataObject
/**
* @param array<string,mixed> $input
*/
public static function fromArray(array $input): static
public static function fromArray(array $input): ?static
{
$parameters = RuleFactory::getParametersMeta(static::class);
foreach ($parameters as $parameter) {
@ -72,8 +72,7 @@ trait DataObject
$validator = static::withValidator($input, $rules);
if ($validator->fails()) {
$exception = new ValidationException($validator);
throw $exception;
return static::fails($validator);
}
$mappedInput = [];
@ -103,6 +102,11 @@ trait DataObject
return [];
}
public static function fails(Validator $validator): ?static
{
throw new ValidationException($validator);
}
/**
* @return array<string,array<int, string|Rule>>
*/

View file

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Tests\Classes;
use Icefox\DTO\DataObject;
use Illuminate\Validation\Validator;
readonly class FailsReturnsDefault
{
use DataObject;
public function __construct(
public string $string,
public int $int = 42,
) {}
public static function fails(Validator $validator): ?static
{
return new self(string: 'default_value');
}
}

View file

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Tests\Classes;
use Icefox\DTO\DataObject;
use Illuminate\Validation\Validator;
readonly class FailsReturnsNull
{
use DataObject;
public function __construct(
public string $string,
public int $int,
) {}
public static function fails(Validator $validator): ?static
{
return null;
}
}

View file

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Tests\Classes;
use Icefox\DTO\DataObject;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Validation\Validator;
readonly class FailsWithHttpResponse
{
use DataObject;
public function __construct(
public string $string,
public int $int,
) {}
public static function fails(Validator $validator): ?static
{
throw new HttpResponseException(
response()->json(['errors' => $validator->errors()], 422)
);
}
}

View file

@ -0,0 +1,108 @@
<?php
namespace Tests\FailedValidation;
use Illuminate\Validation\ValidationException;
use Tests\Classes\FailsReturnsDefault;
use Tests\Classes\FailsReturnsNull;
use Tests\Classes\FailsWithHttpResponse;
use Tests\Classes\PrimitiveData;
describe('fails method behavior', function () {
it('throws ValidationException when class does not implement fails()', function () {
expect(function () {
PrimitiveData::fromArray([
'int' => 0,
'float' => 3.14,
'bool' => true,
]);
})->toThrow(ValidationException::class);
});
it('returns null when fails() returns null', function () {
$result = FailsReturnsNull::fromArray([
'int' => 0,
]);
expect($result)->toBeNull();
});
it('returns static instance when fails() returns an object', function () {
$result = FailsReturnsDefault::fromArray([
'int' => 0,
]);
expect($result)->toBeInstanceOf(FailsReturnsDefault::class);
expect($result->string)->toBe('default_value');
expect($result->int)->toBe(42);
});
});
describe('HTTP request handling', function () {
beforeEach(function () {
\Illuminate\Support\Facades\Route::post('/test-validation-exception', function () {
PrimitiveData::fromArray([
'int' => 0,
'float' => 3.14,
'bool' => true,
]);
return response()->json(['success' => true]);
});
\Illuminate\Support\Facades\Route::post('/test-http-response-exception', function () {
FailsWithHttpResponse::fromArray([
'int' => 0,
]);
return response()->json(['success' => true]);
});
\Illuminate\Support\Facades\Route::post('/test-validation-exception-html', function () {
PrimitiveData::fromArray([
'int' => 0,
'float' => 3.14,
'bool' => true,
]);
return response('success');
});
});
it('returns 422 with errors when ValidationException is thrown in JSON request', function () {
$response = $this->postJson('/test-validation-exception', [
'int' => 0,
'float' => 3.14,
'bool' => true,
]);
$response->assertStatus(422);
$response->assertJsonValidationErrors(['string']);
});
it('returns custom JSON response when HttpResponseException is thrown', function () {
$response = $this->postJson('/test-http-response-exception', [
'int' => 0,
]);
$response->assertStatus(422);
$response->assertJsonStructure(['errors']);
$response->assertJsonFragment([
'errors' => [
'string' => ['The string field is required.'],
],
]);
});
it('redirects back with session errors when ValidationException is thrown in text/html request', function () {
$response = $this->post('/test-validation-exception-html', [
'int' => 0,
'float' => 3.14,
'bool' => true,
], [
'Accept' => 'text/html',
]);
$response->assertRedirect();
$response->assertSessionHasErrors(['string']);
});
});