Framework: [Tests Feature] Assert Exact JSON Structure

Created on 3 Jul 2017  路  6Comments  路  Source: laravel/framework

  • Laravel Version: 5.4.28
  • PHP Version: 7.1.5
  • Database Driver & Version: SQLite 3 (:memory:)

Description

Currently, I'm using assertJsonStructure in order to validate the structure, but it doesn't check the exact JSON structure, for example:

[
  {"id":1, "name": "Bueno Aires", "slug": "buenos-aires"},
  {"id":2, "name": "Neuqu茅n", "slug": "neuquen"},
  {"id":3, "name": "C贸rdoba", "slug": "cordoba"}
]

Test results are:

assertJsonStructure([['id', 'name', 'slug']]);: true
assertJsonStructure([['id', 'name']]);: true

Test results expected:

assertJsonStructure([['id', 'name', 'slug']]);: true
assertJsonStructure([['id', 'name']]);: false

Is there some way to validate that?

Most helpful comment

Here is my solution.

<?php

namespace Tests\Support;

use Illuminate\Foundation\Testing\TestResponse;
use PHPUnit\Framework\Assert as PHPUnit;

trait AssertJson
{
    public function setUpAssertJson()
    {
        TestResponse::macro('assertJsonStructureExact', function (array $structure = null, $responseData = null)
        {
            if (is_null($structure)) {
                return $this->assertJson($this->json());
            }

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

            if (! array_key_exists('*', $structure)) {
                $keys = array_map(function ($value, $key) {
                    return is_array($value) ? $key : $value;
                }, $structure, array_keys($structure));

                PHPUnit::assertEquals($keys, array_keys($responseData));
            }

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

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

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

            return $this;
        });
    }
}

Usage:

class MyTestCase extends TestCase
{
    use AssertJson;

    public function setUp(): void
    {
        parent::setUp();

        $this->setUpAssertJson();
    }

    public function text_example()
    {
        $this->get('api/users')->assertJsonStructureExact(['data' => ['*' => ['id', 'email']]]);
    }
}

All 6 comments

Sorry please ask on the forums, this repo is for bug reporting only.

thanks @themsaid

For future related issues, https://laravel.io/forum/assert-exact-json-structure

Here is my solution.

<?php

namespace Tests\Support;

use Illuminate\Foundation\Testing\TestResponse;
use PHPUnit\Framework\Assert as PHPUnit;

trait AssertJson
{
    public function setUpAssertJson()
    {
        TestResponse::macro('assertJsonStructureExact', function (array $structure = null, $responseData = null)
        {
            if (is_null($structure)) {
                return $this->assertJson($this->json());
            }

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

            if (! array_key_exists('*', $structure)) {
                $keys = array_map(function ($value, $key) {
                    return is_array($value) ? $key : $value;
                }, $structure, array_keys($structure));

                PHPUnit::assertEquals($keys, array_keys($responseData));
            }

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

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

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

            return $this;
        });
    }
}

Usage:

class MyTestCase extends TestCase
{
    use AssertJson;

    public function setUp(): void
    {
        parent::setUp();

        $this->setUpAssertJson();
    }

    public function text_example()
    {
        $this->get('api/users')->assertJsonStructureExact(['data' => ['*' => ['id', 'email']]]);
    }
}

Here is my solution.

<?php

namespace Tests\Support;

use Illuminate\Foundation\Testing\TestResponse;
use PHPUnit\Framework\Assert as PHPUnit;

trait AssertJson
{
    public function setUpAssertJson()
    {
        TestResponse::macro('assertJsonStructureExact', function (array $structure = null, $responseData = null)
        {
            if (is_null($structure)) {
                return $this->assertJson($this->json());
            }

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

            if (! array_key_exists('*', $structure)) {
                $keys = array_map(function ($value, $key) {
                    return is_array($value) ? $key : $value;
                }, $structure, array_keys($structure));

                PHPUnit::assertEquals($keys, array_keys($responseData));
            }

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

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

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

            return $this;
        });
    }
}

Usage:

class MyTestCase extends TestCase
{
    use AssertJson;

    public function setUp(): void
    {
        parent::setUp();

        $this->setUpAssertJson();
    }

    public function text_example()
    {
        $this->get('api/users')->assertJsonStructureExact(['data' => ['*' => ['id', 'email']]]);
    }
}

Maybe, this post is closed and old, but I want shared the updated code for future users:

<?php
namespace Tests\Support;

use Illuminate\Foundation\Testing\TestResponse;
use Illuminate\Support\Arr;
use PHPUnit\Framework\Assert as PHPUnit;

trait AssertJson
{
    public function setUpAssertJson(): void
    {
        TestResponse::macro('assertJsonStructureExact', function (array $structure = null, $responseData = null) {
            if ($structure === null) {
                return $this->assertJson($this->json());
            }

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

            if (!array_key_exists('*', $structure)) {
                $keys = array_map(static function ($value, $key) {
                    return is_array($value) ? $key : $value;
                }, $structure, array_keys($structure));

                PHPUnit::assertEquals(Arr::sortRecursive($keys), Arr::sortRecursive(array_keys($responseData)));
            }

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

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

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

            return $this;
        });
    }
}

If you're using Laravel 7, TestResponse has moved. The new namespace is Illuminate\Testing\TestResponse.

You can actually move the setup function into the trait itself.
I create a gist based on the example for anyone wanting to grab the code

https://gist.github.com/Oldenborg/dbea4eb6df6cabf388310e6b0168262f

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ThomHurks picture ThomHurks  路  75Comments

rafaelrenanpacheco picture rafaelrenanpacheco  路  64Comments

sebastianbergmann picture sebastianbergmann  路  93Comments

JosephSilber picture JosephSilber  路  176Comments

nkeena picture nkeena  路  75Comments