'Laravel Collections. Is there some kind of assertStructure method?

I'm writing tests and I want to assert, that a returned collection has some specific structure.

For asserting jsons I'm using assertJsonStructure() method on the Responce object.

I have not found similar for the \Illuminate\Support\Collection. Did I miss some package/framework method.

An example of what do I want

$collection = collect([
    'name'      => 'John',
    'surname'   => 'Smith',
    'birthoday' => [
        'day'   => 23,
        'month' => 5,
        'year'  => 1970,
    ],
]);

$collection->assertStructure([          //true
    'name',
    'surname',
    'birthday' => ['day', 'month', 'year'],
]);

I will accept

no

as an answer too, but if it is with an example of how to validate such a nested collection.



Solution 1:[1]

There is no such function on Collection instance, the closest you can do are:

  • check if it has a key with has()
  • Check if it contains some value with contains()
  • There are other methods to check if something exist but

If you need inspiration, you can get it with the way Laravel implements assertJsonStructure() in /Illuminate/Foundation/Testing/TestResponse.php:

/**
 * Assert that the response has a given JSON structure.
 *
 * @param  array|null  $structure
 * @param  array|null  $responseData
 * @return $this
 */
public function assertJsonStructure(array $structure = null, $responseData = null)
{
    if (is_null($structure)) {
        return $this->assertJson($this->json());
    }

    if (is_null($responseData)) {
        $responseData = $this->decodeResponseJson();
    }

    foreach ($structure as $key => $value) {
        if (is_array($value) && $key === '*') {
            PHPUnit::assertInternalType('array', $responseData);

            foreach ($responseData as $responseDataItem) {
                $this->assertJsonStructure($structure['*'], $responseDataItem);
            }
        } elseif (is_array($value)) {
            PHPUnit::assertArrayHasKey($key, $responseData);

            $this->assertJsonStructure($structure[$key], $responseData[$key]);
        } else {
            PHPUnit::assertArrayHasKey($value, $responseData);
        }
    }

    return $this;
}

As you can see there is a recursive calls to check the structure in case there is sub-structure.

UPDATE:

As a basic test to solve your question, I modified the assertJsonStructure() to have assertArrayStructure() and this working test:

/**
 * A basic test example.
 *
 * @return void
 */
public function testBasicTest()
{
    $collect = collect(['name' => '1', 'detail' => ['age' => 1,'class' => 'abc']]);

    $this->assertArrayStructure(['name', 'detail' => ['class', 'age']], $collect->toArray());
}


/**
 * Assert the array has a given structure.
 *
 * @param  array  $structure
 * @param  array  $arrayData
 * @return $this
 */
public function assertArrayStructure(array $structure, array $arrayData)
{
    foreach ($structure as $key => $value) {
        if (is_array($value) && $key === '*') {
            $this->assertInternalType('array', $arrayData);

            foreach ($arrayData as $arrayDataItem) {
                $this->assertArrayStructure($structure['*'], $arrayDataItem);
            }
        } elseif (is_array($value)) {
            $this->assertArrayHasKey($key, $arrayData);

            $this->assertArrayStructure($structure[$key], $arrayData[$key]);
        } else {
            $this->assertArrayHasKey($value, $arrayData);
        }
    }

    return $this;
}

Solution 2:[2]

The answer is no, you can simply look for assertive laravel methods at the API documentation, there is no method at the namespace Illuminate\Support\Collection which makes what you are looking for. (You can find Laravel assertive method here)

As a viable alternative, why you don't just serialize your collection and check it with the assertJsonStructure() method?

You could use the response() helper to populate a Illuminate/Foundation/Testing/TestResponse:

use Illuminate/Foundation/Testing/TestResponse;

$testResponse = new TestResponse(response()->json($collection->toArray());
return $testResponse->assertJsonStructure([
    'name',
    'surname',
    'birthday' => ['day', 'month', 'year'],
]);

How I came to this solution:

  • You need the exact same object that returns the method $this->json() in a test, which comes from the trait MakesHttpRequests right here.
  • As the method comment specifies, it returns a Illuminate\Foundation\Testing\TestResponse.
  • Look for the constructor at the docs and see what it needs, a generic response object, Illuminate/Http/Response.

Hope this helps you.

Solution 3:[3]

As of Laravel 8.x, the other answers are somewhat outdated. You can use AssertableJsonString as TestResponse uses that under the hood:

(new \Illuminate\Testing\AssertableJsonString($yourJson))->assertStructure($yourStructure);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 shaedrich