0

I'm trying to validate a required (possibly) empty array in my FormRequest.

I'm usign the present and array validation rules, since i want the value to be passed, but it coul be an empty array.

The problem is that my test fail on an empty string (i.e.: ''), telling me that my request doesn't throw an expected ValidationException with that value.

My test cover the following values for the given field:

input value expected result test outcome
null error present passed
'a string' error present passed
'' error present failed
123 error present passed
123.456 error present passed
true error present passed
[] error not present passed
['a', 'b'] error not present passed

How can i test that expected request parameter is present, is an array and is possibly an empty array?

Update #1: My Code

The request

class TestRequest extends FormRequest
{
    public function rules()
    {
        return [
            'array_field' => ['present', 'array'],
        ];
    }
}

The test


    public function testTest()
    {
        $tests = [
            [
                'value'     => null,
                'outcome'  => 'failure',
                'message'   => 'Failed asserting that request returned error for when null is passed.',
            ],
            [
                'value'     => 'string',
                'outcome'  => 'failure',
                'message'   => 'Failed asserting that request returned error when non empty string is passed.',
            ],
            [
                'value'     => '',
                'outcome'  => 'failure',
                'message'   => 'Failed asserting that request returned error when empty string is passed.',
            ],
            [
                'value'     => 123,
                'outcome'  => 'failure',
                'message'   => 'Failed asserting that request returned error when integer is passed.',
            ],
            [
                'value'     => 123.456,
                'outcome'  => 'failure',
                'message'   => 'Failed asserting that request returned error when float is passed.',
            ],
            [
                'value'     => true,
                'outcome'  => 'failure',
                'message'   => 'Failed asserting that request returned error when boolean is passed.',
            ],
            [
                'value'     => [],
                'outcome'  => 'success',
                'message'   => 'Failed asserting that request returned no error when empty array is passed.',
            ],
            [
                'value'     => ['a', 'b'],
                'outcome'  => 'success',
                'message'   => 'Failed asserting that request returned no error when filled array is passed.',
            ],
        ];

        foreach ($tests as $test) {
            try {
                $request = new TestRequest([
                    'array_field' => $test['value']
                ]);

                $request
                    ->setContainer(app())
                    ->setRedirector(app(Redirector::class))
                    ->validateResolved();
            } catch (ValidationException $e) {
            }

            if ('failure' == $test['outcome']) {
                $this->assertTrue(
                    isset($e),
                    'Failed asserting that request throw an exception for invalid ' . json_encode($test['value']) . ' value.'
                );

                $this->assertArrayHasKey(
                    'array_field',
                    $e->errors(),
                    $test['message']
                );

                unset($e);
            } else {
                $this->assertFalse(
                    isset($e),
                    $test['message']
                );
            }
        }
    }

PhpUnit output

enter image description here

Update #2: the combinations of rules used

I've tested with

  • present + array
  • array
  • required + array

but none of those make the validation pass.

Update #3: the end

Found this old question that depict my same situation: it seems impossible to achive my goal with available validation rules; this comment states that the closer solution is to use ConvertEmptyStringsToNull to convert empty strings to nulls and test validation just against the null value.

fudo
  • 2,254
  • 4
  • 22
  • 44
  • Please also post your piece of code for better understanding. – Kaleem Shoukat Apr 06 '21 at 13:12
  • The test code is quite long, i'll post a shorter version asap, but essentially it iterate over an array of pairs of input value and expected outcome, so i strongly doubt that my problem is related to a logic error in my test, since for all the others values it test behave correctly. – fudo Apr 06 '21 at 13:15

1 Answers1

-1

Okay, a lot of things to change. (Do not take this as an attack, just a constructive reply)

First of all, you are not testing correctly. Remember that a test must only test one thing, here you are testing more than one (no problem with a lot of different values) but you are asserting if it passes and if it fails, that is the problem.

You have to use @dataProvider or @testWith to do multiple tests with different values.

So your test would look like this:

/**
 * @dataProvider inputValues
 */
public function testTest($value)
{
    $response = $this->post('/exampleUrl', ['array_field' => $value]);

    $response->assertSessionHasErrors('array_field');
}

public function inputValues()
{
    return [
        'null is passed' => [null],
        'string is passed' => ['string'],
        'empty string is passed' => [''],
        'integer is passed' => [123],
        'float is passed' => [123.456],
        'boolean is passed' => [true]
    ];
}

This way you will get an error like:

phpunit DataTest
PHPUnit 5.7.0 by Sebastian Bergmann and contributors.

...F

Time: 0 seconds, Memory: 5.75Mb

There was 1 failure:

1) DataTest::testTest with data set "empty string is passed" ('')
Session missing error: array_field.

/home/sb/DataTest.php:9

FAILURES!
Tests: 4, Assertions: 4, Failures: 1.

As you can see, I am not testing "successful" things, as those are separate tests. On the tests you assert if it is working, you are already passing a value that is accepted, so you don't have to test if it has no error with an empty array or an array with data, it is explicit on that test.

Please, add more info about what you are testing, as your TestRequest is super weird, you would not require that and all the further setup you have on it to test it.


So, to solve your issue, your rules are not correct. As the documentation says: "The field under validation must be present in the input data but can be empty.". If you see the source code you will see that it can have an empty value like null or ''. So you want to use the rule array and that's it. It must be present but it has to be an array, it doesn't matter if it is empty or not. You want that !

matiaslauriti
  • 7,065
  • 4
  • 31
  • 43
  • This is not contributing to answer my question, it doesn't explain why `present` and `array` rules are not doing what expected, nor it gives me a set of rules that solve my problem. – fudo Apr 06 '21 at 14:22
  • moreover, with this code i'm not testing routes, requests and controllers in isolation, so a change in any of those may have unwanted/unexpected results on the others, and this test will not tell me where is the arised problem – fudo Apr 06 '21 at 14:24
  • Sorry, I added your fix. Sorry for that ! – matiaslauriti Apr 06 '21 at 14:25
  • If I see `TestRequest` I am assuming you have created your on `Request` class to test a simple request, maybe you are requesting something to an API. Still, it is super weird to see `->setContainer`, `->setRedirector`, and `->validateResolved()`, that is super weird to see, if I see that I will assume you are testing the framework and that is another **no-no** for a test. – matiaslauriti Apr 06 '21 at 14:28
  • sorry but even with just the `array` rule the validation is failing – fudo Apr 06 '21 at 14:28
  • If you add `required` to it ? It would be `['required', 'array']`. – matiaslauriti Apr 06 '21 at 14:29
  • I found this pattern [here](https://stackoverflow.com/a/55389319) and it looked the more compete/correct way to test a request, since it test the full request behaviour – fudo Apr 06 '21 at 14:30
  • the `required` rule will make the validation fail since this rule require the input value to not be empty, and in case of an array it must contains at least one element – fudo Apr 06 '21 at 14:31
  • It makes no sense to allow an empty array or an array with data. If you need data, then require it and it must contain something, so `[]` is not valid, or you don't send it, so you use [optional](https://laravel.com/docs/8.x/validation#a-note-on-optional-fields). – matiaslauriti Apr 06 '21 at 14:34
  • And about the link you shared, **DO NOT DO WHAT IT SAYS**. You are testing the framework there and that is not good, you test your code. If you want 100% test the `FormRequest` you use `$this->post` or `get`, etc. hence a `feature` test, but you do not do what that answers is saying, it is terribly wrong... Can you tell me what are you trying to test ? A controller ? – matiaslauriti Apr 06 '21 at 14:36
  • in general it is not a solution, since this could be an API called by a third party software, so i cannot handle the submitted data and clear out empty data – fudo Apr 06 '21 at 14:37
  • You don't even test a `FormRequest`, you do a `feature` test and see if it throws an error, but never test a `FormRequest`, that is part of the Framework/core, you do not test specifically that because that works. You want to test your logic. – matiaslauriti Apr 06 '21 at 14:37
  • Again, can you explain more about what you are testing ? – matiaslauriti Apr 06 '21 at 14:44