Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Lucky you! You found an early release chapter - it will be fully polished and published shortly!

Customizing Browser Globally

This Chapter isn't
quite ready...

Rest assured, the gnomes are hard at work
completing this video!

Browse Tutorials

Our test works... but the API is sending us back JSON, not JSON-LD. Why?

When we made the GET request earlier, we did not include an Accept header to indicate which format we wanted back. But... JSON-LD is our API's default format, so it sent that back.

However, when we make a ->post() request with the json key, that adds a Content-Type header set to application/json - which is fine - but it also adds an Accept header set to application/json. Yup, we're telling the server that we want plain JSON back, not JSON-LD.

I want to use JSON-LD everywhere. How can we do that? The second argument to ->post() can be an array or an object called HttpOptions. Say HttpOptions::json()... and then pass the array directly. Let me... get my syntax right.

So far, this is equivalent to what we had before. But now we can change some options by saying ->withHeader() passing Accept and application/ld+json.

We could have also done this with the array of options: it has a key called headers. But the object is kind of nice.

Let's make sure this fixes things. Run the test:

symfony php bin/phpunit --filter=testPostToCreateTreasure

Globally Sending the Header

And... we're back to JSON-LD! It's got the right fields and the application/ld+json response Content-Type header.

So.... that's cool... but doing this every time we make a request to our API in the tests is... mega lame. We need this to happen automatically.

A nice way to do that is to leverage a base test class. Inside of tests/, actually inside of tests/Functional/, create a new PHP class called ApiTestCase. I'm going to make this abstract and extend KernelTestCase. Inside, add the HasBrowser trait. But we're going to do something sneaky: we're going to import the browser() method but call it baseKernelBrowser.

Why the heck are we doing that? Re-implement the browser() method... then call $this->baseKernelBrowser() passing it $options and $server. But now call another method: ->setDefaultHttpOptions(). Pass this HttpOptions::create() then ->withHeader(), Accept, application/ld+json.

Done! Back in our real test class, extend ApiTestCase: get the one that's from our app. That's it! When we say $this->browser(), it now calls our browser() method, which changes that default option. Celebrate by removing withHeader()... and you could revert back to the array of options with a json key if you want.

Let's try it.

symfony php bin/phpunit --filter=testPostToCreateTreasure

And... uh oh. That's a strange error:

Cannot override final method _resetBrowserClients()

This... is because we're importing the trait from the parent class and our class... which makes the trait go bananas. Remove the one inside our test class: we don't need it anymore. I'll also do a little cleanup on my use statements.

And now:

symfony php bin/phpunit --filter=testPostToCreateTreasure

Got it! We get back JSON-LD with zero extra work. Remove that dump().

Next: let's write another test that uses our API token authentication.

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
    }
}