Laravel Collections. Is there some kind of assertStructure method?
Asked Answered
L

3

5

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.

Linguini answered 2/11, 2017 at 7:38 Comment(0)
B
3

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;
}
Broderick answered 2/11, 2017 at 8:25 Comment(5)
Thank you. Yes, looks like it's the best and only option for now. Looks like I have a suggestion for Taylor)Linguini
@Linguini That'll be a good idea. However, I attempted to modify that assertJsonStructure() to use array instead. I will update the answer to possible solution - in case you need it urgently,Broderick
@Linguini I have included the solution, just in case you or anyone would find it useful.Broderick
Oh. Thank you. I've already done with phpUnit assertArraySubset() method)Linguini
I'll check your option and will update you, if it works.Linguini
Z
3

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.

Zoroaster answered 2/11, 2017 at 8:12 Comment(4)
@Linguini You are right, sorry, you need a response object, my bad!Zoroaster
Yes, I thought so and checked. It gives Call to undefined method Illuminate\Http\JsonResponse::assertJsonStructure() :)Linguini
@Linguini I'm pretty strong headed, I think that this update could work :DZoroaster
I still think it's quite an overhead, but I've voted for your question) Thank you. I'm sure somebody will find it useful.Linguini
B
3

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;
}
Broderick answered 2/11, 2017 at 8:25 Comment(5)
Thank you. Yes, looks like it's the best and only option for now. Looks like I have a suggestion for Taylor)Linguini
@Linguini That'll be a good idea. However, I attempted to modify that assertJsonStructure() to use array instead. I will update the answer to possible solution - in case you need it urgently,Broderick
@Linguini I have included the solution, just in case you or anyone would find it useful.Broderick
Oh. Thank you. I've already done with phpUnit assertArraySubset() method)Linguini
I'll check your option and will update you, if it works.Linguini
G
2

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);
Gmt answered 22/4, 2022 at 9:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.