If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeNuestra prueba funciona... pero la API nos devuelve JSON, no JSON-LD. ¿Por qué?
Cuando hicimos antes la petición a GET, no incluimos una cabecera Accept para indicar qué formato queríamos de vuelta. Pero... JSON-LD es el formato por defecto de nuestra API, así que nos lo devolvió.
Sin embargo, cuando hacemos una petición a ->post() con la clave json, se añade una cabeceraContent-Type establecida en application/json -lo que está bien-, pero también se añade una cabecera Accept establecida en application/json. Sí, le estamos diciendo al servidor que queremos que nos devuelva JSON plano, no JSON-LD.
Quiero utilizar JSON-LD en todas partes. ¿Cómo podemos hacerlo? El segundo argumento de->post() puede ser una matriz o un objeto llamado HttpOptions. DigamosHttpOptions::json()... y luego pasar directamente el array. A ver... si entiendo bien la sintaxis:
| ... lines 1 - 7 | |
| use Zenstruck\Browser\HttpOptions; | |
| ... lines 9 - 12 | |
| class DragonTreasureResourceTest extends KernelTestCase | |
| { | |
| ... lines 15 - 42 | |
| public function testPostToCreateTreasure(): void | |
| { | |
| ... lines 45 - 52 | |
| ->post('/api/treasures', HttpOptions::json([ | |
| 'name' => 'A shiny thing', | |
| 'description' => 'It sparkles when I wave it in the air.', | |
| 'value' => 1000, | |
| 'coolFactor' => 5, | |
| 'owner' => '/api/users/'.$user->getId(), | |
| ])) | |
| ... lines 60 - 62 | |
| ; | |
| } | |
| } |
Hasta aquí, esto es equivalente a lo que teníamos antes. Pero ahora podemos cambiar algunas opciones diciendo ->withHeader() pasando Accept y application/ld+json:
| ... lines 1 - 12 | |
| class DragonTreasureResourceTest extends KernelTestCase | |
| { | |
| ... lines 15 - 42 | |
| public function testPostToCreateTreasure(): void | |
| { | |
| ... lines 45 - 52 | |
| ->post('/api/treasures', HttpOptions::json([ | |
| 'name' => 'A shiny thing', | |
| 'description' => 'It sparkles when I wave it in the air.', | |
| 'value' => 1000, | |
| 'coolFactor' => 5, | |
| 'owner' => '/api/users/'.$user->getId(), | |
| ])->withHeader('Accept', 'application/ld+json')) | |
| ... lines 60 - 62 | |
| ; | |
| } | |
| } |
También podríamos haberlo hecho con la matriz de opciones: tiene una clave llamadaheaders. Pero el objeto está muy bien.
Asegurémonos de que esto arregla las cosas. Ejecuta la prueba:
symfony php bin/phpunit --filter=testPostToCreateTreasure
Y... ¡volvemos a JSON-LD! Tiene los campos correctos y la respuesta application/ld+jsonEncabezado Content-Type.
Así que .... mola... pero hacer esto cada vez que hacemos una petición a nuestra API en las pruebas es... mega cutre. Necesitamos que esto ocurra automáticamente.
Una buena forma de hacerlo es aprovechar una clase base de prueba. Dentro de tests/, en realidad dentro de tests/Functional/, crea una nueva clase PHP llamada ApiTestCase. Voy a llamarla abstract y extender KernelTestCase:
| ... lines 1 - 2 | |
| namespace App\Tests\Functional; | |
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | |
| ... lines 6 - 9 | |
| abstract class ApiTestCase extends KernelTestCase | |
| { | |
| ... lines 12 - 25 | |
| } |
Dentro, añade el rasgo HasBrowser. Pero vamos a hacer algo astuto: vamos a importar el método browser() pero lo llamaremos baseKernelBrowser:
| ... lines 1 - 7 | |
| use Zenstruck\Browser\Test\HasBrowser; | |
| abstract class ApiTestCase extends KernelTestCase | |
| { | |
| use HasBrowser { | |
| browser as baseKernelBrowser; | |
| } | |
| ... lines 15 - 25 | |
| } |
¿Por qué demonios lo hacemos? Reimplementa el método browser()... luego llama a $this->baseKernelBrowser() pasándole $options y $server. Pero ahora llama a otro método: ->setDefaultHttpOptions(). PásaleHttpOptions::create() y luego ->withHeader(), Accept, application/ld+json:
| ... lines 1 - 5 | |
| use Zenstruck\Browser\HttpOptions; | |
| ... lines 7 - 9 | |
| abstract class ApiTestCase extends KernelTestCase | |
| { | |
| ... lines 12 - 15 | |
| protected function browser(array $options = [], array $server = []) | |
| { | |
| return $this->baseKernelBrowser($options, $server) | |
| ->setDefaultHttpOptions( | |
| HttpOptions::create() | |
| ->withHeader('Accept', 'application/ld+json') | |
| ) | |
| ; | |
| } | |
| } |
¡Listo! De vuelta en nuestra clase de prueba real, extiende ApiTestCase: coge el que es de nuestra app:
| ... lines 1 - 11 | |
| class DragonTreasureResourceTest extends ApiTestCase | |
| { | |
| ... lines 14 - 63 | |
| } |
¡Ya está! Cuando decimos $this->browser(), ahora llama a nuestro métodobrowser(), que cambia esa opción por defecto. Celébralo eliminandowithHeader()... y podrías volver a la matriz de opciones con una clavejson si quieres.
Vamos a probarlo.
symfony php bin/phpunit --filter=testPostToCreateTreasure
Y... uh oh. Es un error extraño:
No se puede anular el método final
_resetBrowserClients()
Esto... es porque estamos importando el trait de la clase padre y de nuestra clase... lo que hace que el trait se vuelva loco. Elimina el que está dentro de nuestra clase de prueba:
| ... lines 1 - 8 | |
| use Zenstruck\Browser\Test\HasBrowser; | |
| ... lines 10 - 11 | |
| class DragonTreasureResourceTest extends ApiTestCase | |
| { | |
| use HasBrowser; | |
| ... lines 15 - 63 | |
| } |
ya no lo necesitamos. También haré una pequeña limpieza en mis sentencias use.
Y ya está:
symfony php bin/phpunit --filter=testPostToCreateTreasure
¡Lo tengo! Volvemos a tener JSON-LD con cero trabajo extra. Elimina ese dump():
| ... lines 1 - 11 | |
| class DragonTreasureResourceTest extends ApiTestCase | |
| { | |
| ... lines 14 - 41 | |
| public function testPostToCreateTreasure(): void | |
| { | |
| ... lines 44 - 45 | |
| $this->browser() | |
| ... lines 47 - 59 | |
| ->dump() | |
| ... line 61 | |
| ; | |
| } | |
| } |
A continuación: vamos a escribir otra prueba que utilice nuestro token de autenticación de la API.
"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
}
}