Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

JSON Test Assertions & Seeding the Database

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Let's make this test real with data and assertions.

There are two main ways to do assertions with Browser. First, it comes with a bunch of built-in methods to help, like ->assertJson(). Or... you can always just grab the JSON that comes back from an endpoint and check things using the built-in PHPUnit assertions you know and love. We'll see both.

Let's start by checking ->assertJson():

... lines 1 - 8
class DragonTreasureResourceTest extends KernelTestCase
{
... lines 11 - 13
public function testGetCollectionOfTreasures(): void
{
$this->browser()
->get('/api/treasures')
... line 18
->assertJson()
;
}
}

When we run that:

symfony php bin/phpunit

It passes! Cool! We know that this response should have a hydra:totalItems property set to the number of results. Right now, our database is empty... but we can at least assert that it matches zero.

To do that, use ->assertJsonMatches().

This is a special method from Browser that uses a special syntax that allows us to read different parts off the JSON. We'll dig into it in a minute.

But this one is simple: assert that hydra:totalItems equals 0:

... lines 1 - 8
class DragonTreasureResourceTest extends KernelTestCase
{
... lines 11 - 13
public function testGetCollectionOfTreasures(): void
{
$this->browser()
... lines 17 - 18
->assertJson()
->assertJsonMatches('hydra:totalItems', 0)
;
}
}

When we try this:

symfony php bin/phpunit

It fails! But with a great error:

mtdowling/jmespath.php is required to search JSON

Hello JMESPath

Ah, we need to install that! Copy the composer require line, find your terminal, and run it:

composer require mtdowling/jmespath.php --dev

This "JMESPath" thing is actually super cool: it's a "query language" for reading different parts of any JSON. For example, if this is your JSON and you want to read the a key, just say a. Simple.

But you can also do deeper, like: a.b.c.d. Or, get crazier: grab the 1 index, or grab a.b.c, then the 0 index, .d, the 1 index then the 0 index. You can even slice the array in different ways. Basically... you can go nuts.

But we're not going to lose our minds with this. It's a handy syntax... but if things get too complex, we can always test the JSON manually, which we'll do in a bit.

Anyway, now that we have the library installed, let's run the test again.

symfony php bin/phpunit

It still fails! With a weird error:

Syntax error at character 5 hydra:totalItems.

Unfortunately, the : is a special character inside of JMESPath. So whenever we have a :, we need to put quotes around that key:

... lines 1 - 8
class DragonTreasureResourceTest extends KernelTestCase
{
... lines 11 - 13
public function testGetCollectionOfTreasures(): void
{
$this->browser()
... lines 17 - 19
->assertJsonMatches('"hydra:totalItems"', 0)
;
}
}

Not ideal, but not a huge inconvenience.

Now when we try it:

symfony php bin/phpunit

It passes!

Seeding the Database

But... this isn't a very interesting test: we're just asserting that we get nothing back... because the database is empty. To make our test real, we need data: we need to seed the database with data at the start of the test.

Tip

To use Foundry factories in a test, also add a use Factories; trait to the top of your test class. Things worked without that in this case, but in the future, you'll likely get an error.

Fortunately, Foundry makes that dead-simple. At the top, call DragonTreasureFactory::createMany() and let's create 5 treasures. Now, below, assert that we get 5 results:

... lines 1 - 4
use App\Factory\DragonTreasureFactory;
... lines 6 - 9
class DragonTreasureResourceTest extends KernelTestCase
{
... lines 12 - 14
public function testGetCollectionOfTreasures(): void
{
DragonTreasureFactory::createMany(5);
$this->browser()
... lines 20 - 22
->assertJsonMatches('"hydra:totalItems"', 5)
... line 24
;
}
}

It's just that simple. And actually, let me put our dump back so we can see the result:

... lines 1 - 9
class DragonTreasureResourceTest extends KernelTestCase
{
... lines 12 - 14
public function testGetCollectionOfTreasures(): void
{
... lines 17 - 18
$this->browser()
... line 20
->dump()
... line 22
->assertJsonMatches('"hydra:totalItems"', 5)
... line 24
;
}
}

Try it now:

symfony php bin/phpunit

It passes! And if you look up, yea! The response has 5 treasures! Dang, that was easy.

Next: let's use JMESPath to assert something more challenging. Then we'll back up and see how we can dig into Browser to give us infinite flexibility - and simplicity - when it comes to testing JSON.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^3.0", // v3.1.2
        "doctrine/annotations": "^2.0", // 2.0.1
        "doctrine/doctrine-bundle": "^2.8", // 2.8.3
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.14", // 2.14.1
        "nelmio/cors-bundle": "^2.2", // 2.2.0
        "nesbot/carbon": "^2.64", // 2.66.0
        "phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
        "phpstan/phpdoc-parser": "^1.15", // 1.16.1
        "symfony/asset": "6.2.*", // v6.2.5
        "symfony/console": "6.2.*", // v6.2.5
        "symfony/dotenv": "6.2.*", // v6.2.5
        "symfony/expression-language": "6.2.*", // v6.2.5
        "symfony/flex": "^2", // v2.2.4
        "symfony/framework-bundle": "6.2.*", // v6.2.5
        "symfony/property-access": "6.2.*", // v6.2.5
        "symfony/property-info": "6.2.*", // v6.2.5
        "symfony/runtime": "6.2.*", // v6.2.5
        "symfony/security-bundle": "6.2.*", // v6.2.6
        "symfony/serializer": "6.2.*", // v6.2.5
        "symfony/twig-bundle": "6.2.*", // v6.2.5
        "symfony/ux-react": "^2.6", // v2.7.1
        "symfony/ux-vue": "^2.7", // v2.7.1
        "symfony/validator": "6.2.*", // v6.2.5
        "symfony/webpack-encore-bundle": "^1.16", // v1.16.1
        "symfony/yaml": "6.2.*" // v6.2.5
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "mtdowling/jmespath.php": "^2.6", // 2.6.1
        "phpunit/phpunit": "^9.5", // 9.6.3
        "symfony/browser-kit": "6.2.*", // v6.2.5
        "symfony/css-selector": "6.2.*", // v6.2.5
        "symfony/debug-bundle": "6.2.*", // v6.2.5
        "symfony/maker-bundle": "^1.48", // v1.48.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/phpunit-bridge": "^6.2", // v6.2.5
        "symfony/stopwatch": "6.2.*", // v6.2.5
        "symfony/web-profiler-bundle": "6.2.*", // v6.2.5
        "zenstruck/browser": "^1.2", // v1.2.0
        "zenstruck/foundry": "^1.26" // v1.28.0
    }
}
userVoice