Lucky you! You found an early release chapter - it will be fully polished and published shortly!
Rest assured, the gnomes are hard at work
completing this video!
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.
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.
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!
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!
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
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.
"Houston: no signs of life"
Start the conversation!
// 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
}
}