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!

Testing Authentication

This Chapter isn't
quite ready...

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

Browse Tutorials

Let's create a test to post and create a new treasure. Say public function testPostToCreateTreasure() that returns void. And start the same way as before: $this->browser()->post('/api/treasures').

In this case we need to send data. The second argument to any of these post() or get() methods is an array of options, which can include headers, query parameters or other stuff. One key is json, which you can set to an array, which will be JSON-encoded for you. Start by sending empty JSON... then ->assertStatus(422). To see what the response looks like, add ->dump().

Awesome! Copy the test method name. I want to focus just on this one test. To do that, run:

symfony php bin/phpunit --filter=testPostToCreateTreasure

And... oh! Current response status code is 401, but 422 expected.

Dumped Failed Responses in Browser

When a test fails with browser, it automatically saves the last response to a file... which is awesome. It's actually in the var/ directory. In my terminal, I can hold command and click to open that in my browser. That is nice. You'll see me do this a bunch of times.

Ok, so this returned a 401 status code. Of course: the endpoint requires authentication! Our app has two ways to authenticate: via the login form and session or via an API token. We're going to test both, starting with the loging form.

Logging in during the Test

To log in as a user... that user first needs to exist in the database. Remember: at the start of each test, our database is empty. It's then our job to populate it with whatever we need.

Create a user with UserFactory::createOne(['password' => 'pass']) so that we know what the password will be. Then, before we make the POST request to create a treasure, ->post() to /login and send json with email set to $user->getEmail() - to use whatever random email address Faker chose - then password set to pass. To make sure that worked, ->assertStatus(204).

That's the status code we're returning after successful authentication.

Let's give this a try! Move over and run the test:

symfony php bin/phpunit --filter=testPostToCreateTreasure

It passes! We're getting the 422 status code and see the validation messages!

Shortcut to Logging in: actingAs()

So... logging in is... just that easy! And I would recommend having a test that specifically POSTs to your login endpoint like we just did, to make sure its working correctly.

However, in all of my other tests... when I simply need to be authenticated to do the real work, there's a faster way to log in. Instead of making the POST request, say ->actingAs($user).

This is a sneaky way of taking the User object and pushing it directly into Symfony's security system without making any requests. It's easier, and faster. And now, we don't care what the password is at all, so we can simplify that.

Let's check it:

symfony php bin/phpunit --filter=testPostToCreateTreasure

Still good!

Testing Successful Treasure Creation

Let's do another POST down here. Keep chaining and add ->post(). Actually... I'm lazy. Copy the existing ->post()... and use that. But this time, send real data: I'll quickly type in some... these can be anything. The last key we need is owner. Right now, we are required to send the owner when we create a treasure. Soon, we'll make that optional: if we don't send it, it will default to whoever is authenticated. But for now, set it to /api/users/ then $user->getId(). Finish with assertStatus(201).

Because 201 is what the API returns when an object is created.

Alright, go-test-go:

symfony php bin/phpunit --filter=testPostToCreateTreasure

Still passing! We're on a roll! Add a ->dump() to help us debug then a sanity check: ->assertJsonMatches() that name is A shiny thing.

When we try that:

symfony php bin/phpunit --filter=testPostToCreateTreasure

Sending the Accept: application/ld+json Header

No surprise: all green. But look at the dumped response: it's not JSON-LD! We're getting back standard JSON. You can see it in the Content-Type header: 'application/json', not application/ld+json, which is what I was expecting.

Let's find out what's going on next and fix it globally by customizing how Browser works across our entire test suite.

Leave a comment!

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