Una de las funcionalidades que, a veces, requerimos en nuestros proyectos WordPress, es la manera de conectarnos a una API REST externa y mostrar los resultados al usuario final.

Escribir pruebas API REST con PHPUnit te dará la confianza al momento de consumir los datos de una API REST externa. Además, en cada cambio o nueva funcionalidad evitarás errores a futuro.

Pruebas API REST con PHPUnit

He creado la clase class-wp-phpunit-pe-api-rest.php donde puedes obtener uno de los personajes de la serie Rick y Morty. Esa clase existe dentro del plugin de prueba.

En esa clase hay dos métodos:

  • get_character( $id ). Obtén el personaje especificando el ID del personaje.
  • cache_response( $response ). Guarda la información del personaje para evitar múltiples llamadas a la API.

A esos dos métodos vamos a ejecutarle pruebas con PHPUnit. ¿Realmente funcionan como deberían?

Pruebas a ejecutar

El archivo de prueba se llama tests/test-class-wp-phpunit-pe-api-rest.php. Y te aseguro que las pruebas de este archivo se vuelven más y más sencillas de escribir.

Para nuestros casos de uso, hay que escribir pruebas para:

  • Obtener el personaje usando la API REST.
  • Cachear el resultado de la consulta API REST.
  • Ver si la respuesta es obtenida desde la cache.
  • Verificar si el ID del personaje es invalida o no existe.

En este post, solo voy a explicar los dos primeros casos de uso. Las demás pruebas siguen los mismos principios que los dos primeros casos de uso y puedes checar el código para ver cómo implemento esas pruebas.

Escribiendo las pruebas

Empezamos con el método setUp(). Ahí vamos a crear un usuario de tipo administrador y asignarlo como el usuario actual:

private $api_rest;
 
public function setUp() {
$user_id = self::factory()->user->create( array( 'role' => 'administrator' ) );
wp_set_current_user( $user_id );
 
$this->api_rest = new WP_PHPUnit_PE_API_REST();
}

Para crear un usuario, hay que usar el factory user, con su método create(). El resultado de esa función retornará el ID del usuario.

Hay que asignar la clase WP_PHPUnit_PE_API_REST() a la propiedad $this->api_rest. Esta propiedad será usada para llamar al método para obtener el personaje desde nuestras pruebas.

Ahora escribe la prueba, test_if_current_character_is_morty(). Esta prueba determinará si el personaje actual es “Morty”.

Aserciones y pruebas para obtener los datos del personaje

Dentro de nuestra prueba tenemos 3 aserciones que serán ejecutadas sobre la respuesta del método get_character( $id ):

public function test_if_current_character_is_morty() {
$character_id = 2;
$character = $this->api_rest->get_character( $character_id );
 
$this->assertNotWPError( $character );
$this->assertIsArray( $character );
$this->assertSame( 'Morty Smith', $character['name'] );
}
FunciónDescripción
assertNotWPError( $character );La respuesta no debe ser una instancia de WP_Error.
assertIsArray( $character );Espera que la respuesta sea un array.
assertSame( $expected, $value );Espera que los dos valores pasados sean idénticos.
Aserciones aplicadas en nuestra prueba.

En el código original del método get_character( $id ) en el archivo class-wp-phpunit-pe-api-rest.php:

public function get_character( $id ) {
if ( ! is_user_logged_in() ) {
return new WP_Error( 'no_user', 'You are not allowed to run this operation.' );
}
 
$id = (int) $id;
 
if ( ! $id ) {
return new WP_Error( 'no_id', 'The character ID is invalid.' );
}
 
$characters = get_option( 'rickandmortyapi_characters', array() );
 
if ( ! empty( $characters ) && isset( $characters[ $id ] ) ) {
return $characters[ $id ];
}
 
$response = wp_remote_get( $this->uri . 'character/' . $id );
 
if ( is_wp_error( $response ) ) {
return new WP_Error( 'internal_error', $response->get_error_message() );
}
 
$valid_http_codes = array( 200 );
$current_http_code = wp_remote_retrieve_response_code( $response );
$response = json_decode( wp_remote_retrieve_body( $response ), true );
 
return ! in_array( $current_http_code, $valid_http_codes, true )
? new WP_Error( 'not_found', $response['error'] )
: $this->cache_response( $response );
}

Incluyo varias condiciones que podrían retornar una instancia de WP_Error:

  • Si el usuario no tiene una sesión iniciada. La operación API REST debe ser ejecutada por usuarios que tengan una sesión iniciada en el sistema. (Línea 16)
  • Si el ID del personaje es válido. Debes checar que la variable $id es válida, es decir, debe ser de tipo int y no un string. (Línea 22)
  • Si la respuesta de la petición API REST fue inválida. Las peticiones remotas a API REST externas no siempre podrían funcionar; debes capturar ese tipo de errores y terminar el programa. (Línea 34)
  • Si el código HTTP no es 200. En nuestro caso, el único código HTTP válido es el 200; otro código como el 404 será incorrecto. (Línea 42)

Si las anteriores validaciones no ocurren, entonces procede a realizar la petición API REST, cachea la respuesta y retorna los datos del personaje buscado.

Las 3 aserciones realizadas en nuestra prueba test_if_current_character_is_morty() deberían ser correctas, aunque si has especificado otro ID de personaje, por ejemplo el ID 1, tendrás que cambiar el nombre esperado de la aserción $this->assertSame().

Si usas el ID 1, el nombre esperado será “Rick Sanchez” y el método luciría de esta manera:

$this->assertSame( 'Rick Sanchez', $character['name'] );

La prueba debería ser exitosa para las 3 aserciones (solo si ejecutas únicamente esa prueba).

Aserciones de PHPUnit exitosas para obtener datos del personaje usando API REST externa.
Aserciones de PHPUnit exitosas para obtener datos del personaje usando API REST externa.

Cache de los datos del personaje

Cuando consumimos API REST externas en WordPress, uno de los aspectos importantes es la “cache”.

Los resultados de nuestras peticiones hay que cachearlas para no estar realizando peticiones cada vez que el usuario entre a nuestra página.

He aplicado el cacheo a la petición API REST en nuestro plugin de prueba, y hay dos partes dentro del código original que quiero que veas:

  1. Si los datos del personaje ya existen en la base de datos, entonces devuelve esos datos y no hagas otra petición remota.
  2. Si realiza la petición remota, cachea los resultados y retórnalos.

El primer punto, en código (líneas 25…29), es lo siguiente:

$characters = get_option( 'rickandmortyapi_characters', array() );
 
if ( ! empty( $characters ) && isset( $characters[ $id ] ) ) {
return $characters[ $id ];
}

Y el segundo punto, en código (línea 43), es el siguiente:

return ! in_array( $current_http_code, $valid_http_codes, true )
? new WP_Error( 'not_found', $response['error'] )
: $this->cache_response( $response );

Como puedes ver, en el segundo punto se manda a llamar el método cache_response( $response ), el cual va a guardar los datos del personaje en la base de datos.

Específicamente, los datos se guardarán en la tabla wp_options bajo el nombre de rickandmortyapi_characters.

Ya que los datos sean guardados, la próxima vez en que se busque el mismo personaje, ya no hará la petición remota, ahora retornará los datos desde la base de datos.

Aserciones y pruebas para cachear los datos del personaje

Para probar la funcionalidad de la cache, he creado el método de prueba test_if_character_response_is_cached() dentro del archivo test-class-wp-phpuni-pe-api-rest.php.

/**
* @depends test_if_current_character_is_morty
*/
public function test_if_character_response_is_cached() {
$character_id = 2;
$characters = get_option( 'rickandmortyapi_characters', array() );
 
$this->assertNotEmpty( $characters );
$this->assertArrayHasKey( $character_id, $characters );
$this->assertSame( 'Morty Smith', $characters[ $character_id ]['name'] );
}

Lo más resaltante de esta prueba, es que en sus comentarios tiene @depends:

/**
* @depends test_if_current_character_is_morty
*/

Esto quiere decir que cuando este método se ejecute, primero debe ejecutar la prueba test_if_current_character_is_morty(), así podremos leer los resultados guardados por este método.

El plugin siempre cachea los datos de un nuevo personaje en cada petición, al final, va a ejecutar el método cache_response():

private function cache_response( $response ) {
$characters = get_option( 'rickandmortyapi_characters', array() );
 
$characters[ $response['id'] ] = $response;
 
update_option( 'rickandmortyapi_characters', $characters );
 
return $response;
}

Ahora, como mencionamos antes, los datos del personajes se guardan en la opción rickandmortyapi_characters de la tabla wp_options.

A continuación hay que verificar si los datos del personaje se están cacheando/guardando correctamente en la base de datos.

Para esto, hay que ejecutar 3 aserciones:

FunciónDescripción
assertNotEmpty( $characters );El array de los datos de los personajes no debe estar vacío.
assertArrayHasKey( $character_id, $character );Espera que el ID del personaje exista dentro del array.
assertSame( $expected, $value );Espera que los dos valores pasados sean idénticos.
Aserciones aplicadas en nuestra prueba.

Para cumplir las aserciones anteriores, hay que enfocarnos en el código:

$character_id = 2;
$characters = get_option( 'rickandmortyapi_characters', array() );

La variable $characters va ser igual a los datos de los personajes almacenados en la opción rickandmortyapi_characters de la tabla wp_options. Si la opción no existe, entonces se le asignará un array vacío.

Si la variable $characters no es vacío, entonces nuestra primera aserción la hemos pasado y si el ID del personaje existe como llave del array, ya llevamos dos aserciones correctas.

La tercera aserción, es una simple comparación entre el nombre del personaje esperado y la respuesta guardada en la base de datos. Si todo está bien, las 3 aserciones deben ser correctas.

En la siguiente imagen, verás como se han ejecutado dos pruebas y 6 aserciones, esto debido a la cláusula @depends:

Aserciones de PHPUnit exitosas para cachear datos del personaje usando API REST externa.
Aserciones de PHPUnit exitosas para cachear datos del personaje usando API REST externa.

No olvides que hace falta ejecutar más pruebas a nuestra funcionalidad de API REST. Ya existen en el archivo de pruebas, ahora te toca jugar un poco con ellas.

0

Escrito por Roel Magdaleno

Desarrollador Backend y Performance Engineer.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *