Skip to content
31 changes: 31 additions & 0 deletions src/Illuminate/Testing/TestResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use ArrayAccess;
use Closure;
use Illuminate\Contracts\Validation\Rule as RuleContract;
use Illuminate\Contracts\View\View;
use Illuminate\Cookie\CookieValuePrefix;
use Illuminate\Database\Eloquent\Model;
Expand All @@ -18,6 +19,7 @@
use Illuminate\Testing\Assert as PHPUnit;
use Illuminate\Testing\Constraints\SeeInOrder;
use Illuminate\Testing\Fluent\AssertableJson;
use Illuminate\Validation\Validator;
use LogicException;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\StreamedResponse;
Expand Down Expand Up @@ -914,6 +916,35 @@ public function assertJsonValidationErrorFor($key, $responseKey = 'errors')
return $this;
}

/**
* Assert that the response has the given JSON validation errors.
*
* @param string|array $attribute
* @param string|\Illuminate\Contracts\Validation\Rule|null $rule
* @param string $responseKey
* @return $this
*/
public function assertJsonValidationErrorRule(string|array $attribute, string|RuleContract|null $rule = null, $responseKey = 'errors')
{
$validationRules = $attribute;

if (is_string($attribute)) {
PHPUnit::assertNotNull($rule, 'No validation rule was provided.');

$validationRules = [$attribute => $rule];
}

$validator = new Validator(app('translator'), [], []);

foreach ($validationRules as $attribute => $rule) {
$this->assertJsonValidationErrors([
$attribute => $validator->getErrorMessage($attribute, $rule),
], $responseKey);
}

return $this;
}

/**
* Assert that the response has no JSON validation errors for the given keys.
*
Expand Down
54 changes: 54 additions & 0 deletions src/Illuminate/Validation/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,60 @@ public function setFallbackMessages(array $messages)
$this->fallbackMessages = $messages;
}

/**
* Get the error messages for an attribute and a validation rule.
*
* @param string $attribute
* @param string|\Illuminate\Contracts\Validation\Rule $rule
* @return array
*/
public function getErrorMessage(string $attribute, string|RuleContract $rule): array
{
[$rule, $parameters] = ValidationRuleParser::parse($rule);
$result = [];

if ($rule === '') {
return $result;
}

// First we will get the correct keys for the given attribute in case the field is nested in
// an array. Then we determine if the given rule accepts other field names as parameters.
// If so, we will replace any asterisks found in the parameters with the correct keys.
if ($this->dependsOnOtherFields($rule)) {
$parameters = $this->replaceDotInParameters($parameters);

if ($keys = $this->getExplicitKeys($attribute)) {
$parameters = $this->replaceAsterisksInParameters($parameters, $keys);
}
}

if ($rule instanceof RuleContract) {
$messages = $rule->message();

$messages = $messages ? (array) $messages : [get_class($rule)];

foreach ($messages as $message) {
$result[] = $this->makeReplacements(
$message, $attribute, get_class($rule), []
);
}

return $result;
}

$attribute = str_replace(
[$this->dotPlaceholder, '__asterisk__'],
['.', '*'],
$attribute
);

return [
$this->makeReplacements(
$this->getMessage($attribute, $rule), $attribute, $rule, $parameters
),
];
}

/**
* Get the Presence Verifier implementation.
*
Expand Down
76 changes: 76 additions & 0 deletions tests/Integration/Testing/TestResponseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

namespace Illuminate\Tests\Integration\Testing;

use Illuminate\Contracts\Validation\Rule as RuleContract;
use Illuminate\Http\Response;
use Illuminate\Testing\TestResponse;
use Orchestra\Testbench\TestCase;
use PHPUnit\Framework\ExpectationFailedException;

class TestResponseTest extends TestCase
{
public function testassertJsonValidationErrorRuleWithString()
{
$data = [
'status' => 'ok',
'errors' => ['key' => 'The key field is required.'],
];

$testResponse = TestResponse::fromBaseResponse(
(new Response)->setContent(json_encode($data))
);

$testResponse->assertJsonValidationErrorRule('key', 'required');
}

public function testassertJsonValidationErrorRuleWithArray()
{
$data = [
'status' => 'ok',
'errors' => ['key' => 'The key field is required.'],
];

$testResponse = TestResponse::fromBaseResponse(
(new Response)->setContent(json_encode($data))
);

$testResponse->assertJsonValidationErrorRule(['key' => 'required']);
}

public function testassertJsonValidationErrorRuleWithCustomRule()
{
$rule = new class implements RuleContract
{
public function passes($attribute, $value)
{
return true;
}

public function message()
{
return ':attribute must be baz';
}
};

$data = [
'status' => 'ok',
'errors' => ['key' => 'key must be baz'],
];

$testResponse = TestResponse::fromBaseResponse(
(new Response)->setContent(json_encode($data))
);

$testResponse->assertJsonValidationErrorRule('key', $rule);
}

public function testassertJsonValidationErrorRuleWithNoRule()
{
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('No validation rule was provided.');

$response = TestResponse::fromBaseResponse(new Response());
$response->assertJsonValidationErrorRule('foo');
}
}
48 changes: 48 additions & 0 deletions tests/Validation/ValidationValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7096,6 +7096,54 @@ public function testArrayKeysValidationFailsWithNotAnArray()
);
}

public function testGetErrorMessageWithBuiltinRule()
{
$trans = $this->getIlluminateArrayTranslator();
$trans->addLines([
'validation.required_array_keys' => 'The :attribute field must contain entries for :values',
'validation.required' => 'The :attribute field is required.',
], 'en');

$validator = new Validator($trans, [], []);

$this->assertSame(
['The foo field is required.'],
$validator->getErrorMessage('foo', 'required')
);

$this->assertSame(
['The foo field must contain entries for bar, baz'],
$validator->getErrorMessage('foo', 'required_array_keys:bar,baz')
);
}

public function testGetErrorMessageWithCustomRule()
{
$rule = new class implements Rule
{
public function passes($attribute, $value)
{
return true;
}

public function message()
{
return ':attribute must be baz';
}
};

$validator = new Validator(
$this->getIlluminateArrayTranslator(),
[],
[]
);

$this->assertSame(
['foo must be baz'],
$validator->getErrorMessage('foo', $rule)
);
}

protected function getTranslator()
{
return m::mock(TranslatorContract::class);
Expand Down