Buy Access to Course
16.

Aserciones de prueba JSON avanzadas y flexibles

|

Share this awesome video!

|

Keep on Learning!

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

Login Subscribe

También podríamos querer probar que obtenemos los campos correctos en la respuesta para cada elemento. ¿Podemos hacerlo con JMESPath? ¡Claro que sí! El método assertJsonMatches() es realmente práctico. Y en realidad, si mantienes pulsado comando o control y haces clic en él, cuando llamamos a assertJsonMatches(), entre bastidores, llama a$this->json(). Esto crea un objeto Json... que tiene métodos aún más útiles. La propia instancia Browser nos da acceso a assertJsonMatches(). Pero si queremos utilizar cualquiera de sus otros métodos, tenemos que hacer un poco más de trabajo.

La primera forma de utilizar el objeto Json es a través del método use() del navegador. Pásale una llamada de retorno con un argumento Json $json:

// ... lines 1 - 6
use Zenstruck\Browser\Json;
// ... lines 8 - 10
class DragonTreasureResourceTest extends KernelTestCase
{
// ... lines 13 - 15
public function testGetCollectionOfTreasures(): void
{
// ... lines 18 - 19
$this->browser()
// ... lines 21 - 24
->use(function(Json $json) {
// ... line 26
})
;
}
}

Esta es una característica mágica del navegador: lee la sugerencia de tipo del argumento y sabe que debe pasarnos el objeto Json. También podrías pasarle un objeto CookieJar,Crawler o algunas otras cosas.

La cuestión es que, como hemos indicado el argumento con Json, cogerá el objetoJson de la última respuesta y nos lo pasará. Vamos a utilizarlo para hacer algunos experimentos. Queremos comprobar cuáles son las claves del primer elemento dentro de hydra:member. Para ayudarnos a averiguar la expresión que necesitamos, vamos a utilizar un método llamado search(). Esto nos permite utilizar una expresión JMESPath y obtener el resultado. Haz las comillas dobles y luego hydra:member para ver lo que devuelve. Y... elimina el otro volcado:

// ... lines 1 - 10
class DragonTreasureResourceTest extends KernelTestCase
{
// ... lines 13 - 15
public function testGetCollectionOfTreasures(): void
{
// ... lines 18 - 19
$this->browser()
// ... lines 21 - 24
->use(function(Json $json) {
dump($json->search('"hydra:member"'));
})
;
}
}

¡Vale! Ejecuta de nuevo la prueba:

symfony php bin/phpunit

Pasa... pero lo más importante, ¡mira el volcado! Es la matriz de 5 elementos. Ok... vamos a coger el índice 0. Después de las comillas dobles hydra:member, añade[0]. A continuación, rodea todo con una función keys() de JMESPath:

// ... lines 1 - 10
class DragonTreasureResourceTest extends KernelTestCase
{
// ... lines 13 - 15
public function testGetCollectionOfTreasures(): void
{
// ... lines 18 - 19
$this->browser()
// ... lines 21 - 24
->use(function(Json $json) {
dump($json->search('keys("hydra:member"[0])'));
})
;
}
}

Pruébalo ahora:

symfony php bin/phpunit

Qué bonito. Y probablemente sea una de las cosas más complejas que harás. Ahora que tenemos la ruta correcta, conviértela en una aserción. Puedes hacerlo estableciendo esto en una variable -como $keys - y utilizando una aserción normal. O puedes cambiar search por assertMatches() y pasarle un segundo argumento: la matriz de los campos esperados:

// ... lines 1 - 10
class DragonTreasureResourceTest extends KernelTestCase
{
// ... lines 13 - 15
public function testGetCollectionOfTreasures(): void
{
// ... lines 18 - 19
$this->browser()
// ... lines 21 - 24
->use(function(Json $json) {
$json->assertMatches('keys("hydra:member"[0])', [
'@id',
'@type',
'name',
'description',
'value',
'coolFactor',
'owner',
'shortDescription',
'plunderedAtAgo',
]);
})
;
}
}

¡Ya está! Pruébalo:

symfony php bin/phpunit

¡Pasa! Y sí, ahora podríamos eliminar el método use() y pasar esto a una llamada normal a ->assertJsonMatches().

Hacer aserciones JSON normales

Por muy guay que sea esto de JMESPath, es otra cosa que hay que aprender y puede resultar complejo. ¿Cuál es la alternativa?

Asignar toda la cadena $browser a una nueva variable $json y luego añadir ->json()al final. La mayoría de los métodos de Browser devuelven... un Browser, lo que nos permite hacer todo el encadenamiento divertido. Pero unos pocos, como ->json() nos permiten "salir" del navegador para que podamos hacer algo personalizado.

Esto nos permite eliminar aquí la función use() y sustituir las aserciones por código PHPUnit más tradicional:

// ... lines 1 - 10
class DragonTreasureResourceTest extends KernelTestCase
{
// ... lines 13 - 15
public function testGetCollectionOfTreasures(): void
{
DragonTreasureFactory::createMany(5);
$json = $this->browser()
->get('/api/treasures')
->assertJson()
->assertJsonMatches('"hydra:totalItems"', 5)
->assertJsonMatches('length("hydra:member")', 5)
->json()
;
$json->assertMatches('keys("hydra:member"[0])', [
'@id',
'@type',
'name',
'description',
'value',
'coolFactor',
'owner',
'shortDescription',
'plunderedAtAgo',
]);
}
}

Podríamos seguir utilizando directamente el objeto Json... que pasa... o para eliminar toda fantasía, cambiar a $this->assertSame() que$json->decoded()['hydra:member'][0] - array_keys() alrededor de todo - coincide con nuestra matriz:

// ... lines 1 - 10
class DragonTreasureResourceTest extends KernelTestCase
{
// ... lines 13 - 15
public function testGetCollectionOfTreasures(): void
{
DragonTreasureFactory::createMany(5);
$json = $this->browser()
->get('/api/treasures')
->assertJson()
->assertJsonMatches('"hydra:totalItems"', 5)
->assertJsonMatches('length("hydra:member")', 5)
->json()
;
$this->assertSame(array_keys($json->decoded()['hydra:member'][0]), [
'@id',
'@type',
'name',
'description',
'value',
'coolFactor',
'owner',
'shortDescription',
'plunderedAtAgo',
]);
}
}

Y por supuesto... ¡que pase a!

Así pues, mucha potencia... pero también mucha flexibilidad para escribir pruebas como quieras.

A continuación, vamos a añadir pruebas para la autenticación: tanto para iniciar sesión a través de nuestro formulario de acceso como a través de un token de API.