Cómo probar traits existentes en Laravel

Spread the love

¿Cómo pruebas tus traits en Laravel?

Los Traits son geniales para compartir parte del comportamiento de una clase sin tener que extender de nada. Es como un copy/paste pero sin la duplicación de código ya que el código esta en el Trait.

Esto también ayuda a organizar el código de una clase. En Laravel puedes encontrar varios ejemplos donde se aplica esta idea.

Los Traits son populares pero se prueban poco y tiene sentido porque generalmente llegamos aun Trait después de una ronda de refactorización y digamos que hemos probado ese triat de forma indirecta (Probamos la clase que lo usa).

pero no es mala idea tener pruebas de nuestros traits si queremos generalizar su uso en otras clases y ayudan cuando el trait crece y tiene que evolucionar a una clase.

Además las pruebas de esos traits nos sirven para documentar el progreso del proyecto.

Así que el día de hoy vamos a ver dos estrategias que puedes usar para probar tu Trait de una forma mas aislada.

Video

El video es un ejemplo de como agregar pruebas usando clases anonimas y el trait surge de una serie de refactorizaciones.

Cómo probar tus traits existentes

¿Qué es un trait?

Los traits en PHP son una especie de clases abstractas que ayudan a reutilizar código y a separar funcionalidades del resto de clases. Esta característica fue añadida a partir de la versión 5.4 de PHP.

La ventaja que ofrece un trait es la de reutilizar código sin la necesidad de instanciar una clase, solo agregas una línea de código en tu clase y ya tienes todo el poder de tu trait.

¿Cómo se define un trait en Laravel?

La mejor forma de definir un trait en Laravel es crear una carpeta llamada Traits en la carpeta app, con el fin de organizar tu código y si es necesario moverla en el futuro sea mucho más sencillo.

Teniendo esta convención crearías un namespace llamado App\Traits y así mandamos llamar al trait en cada clase que lo requiera.

A continuación te enseñaré como probar tus Traits en Laravel:

¿Cómo probar mi Trait?

El problema con los trait es que deben de estar acoplados a una clase que los use. Afortunadamente puedes usar un sustituto que nos ayuda a prescindir de una clase real (Si es necesario) y probar nuestro trait de forma mas aislada.

Vamos a ver dos opciones empleado clases anónimas y la otra es incrustar el trait a nuestra prueba.

Vamos a suponer que tenemos un trait que se encarga de proporcionar un método para gestionar la subida de una imagen y asignarle un nombre por defecto.

namespace App\Traits;


use Illuminate\Http\Request;
use Illuminate\Support\Str;

trait HasAvatar
{
    /**
     * @param Request $request
     */
    public function saveAvatar(Request $request): void
    {
        $this->avatar = $request->file('avatar')->storeAs(
            'avatars',
            $this->defaultName($request)
        );
        $this->save();
    }

    /**
     * @param Request $request
     * @return string
     */
    public function defaultName(Request $request): string
    {
        return sprintf('avatar-%s.%s', Str::uuid(), $request->file('avatar')->extension());
    }

}

Nuestro trait es sencillo, pero voy a tomar el método defultName para el ejemplo.

Para probar el método antes, necesitamos resolver algunas dependencias, como tener un Request y lograr que la clase Str::uuid me regrese una cadena de forma controlada.

Para el request solo tenemos que crear una instancia que contenga una imagen y esto lo hacemos de la siguiente forma:

    /**
     * @return Request
     */
    public function getRequestWithFile(): Request
    {
        return Request::create('/test', 'POST', [], [], [
            'avatar' => UploadedFile::fake()->image('avatar.png'),
        ]);
    }

Para la clase Str, tiene un método que nos ayuda a obtener un “uuid” que no sea real pero que nos sirve para evaluar la prueba.

        Str::createUuidsUsing(function(){
            return 'fake-uuid';
        });

para validar que el método defaultName realiza la tarea que queremos, necesitamos buscar la forma de tener acceso a los métodos del trait y esto lo haremos de forma sencilla con una clase anónima

y para este caso solo se requiere que la clase use nuestro trait:

$user = new class {
    use HasAvatar;
}

Agregando todo a nuestra prueba quedaría de la siguiente forma

    public function test_creates_default_filename()
    {
        $request = $this->getRequestWithFile();

        Str::createUuidsUsing(function(){
            return 'fake-uuid';
        });

        $user = new class {
            use HasAvatar;
        }

        $this->assertSame('avatar-fake-uuid.png', $user->defaultName($request));
    }

Ahora ya podemos ejecutar la prueba que para este caso debe de pasar, con esta misma estrategia podemos probar el método saveAvatar().

Agrega el Trait a la prueba

Para probar de esta forma solo tienes que agregar el trait a tu prueba con un use:

namespace Tests\Feature;

use Tests\TestCase;
use App\Traits\HasAvatar;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;

class HasAvatarTest extends TestCase
{
    use HasAvatar;
    
    //...
}

y ajustar la prueba para que se llame el método mediante $this.

public function test_creates_default_filename()
    {
        $request = $this->getRequestWithFile();

        Str::createUuidsUsing(function(){
            return 'fake-uuid';
        });

        $this->assertSame('avatar-fake-uuid.png', $this->defaultName($request));
    }

El problema de esta opción es que debes de asegurarte que los métodos del trait no hacen colisión con alguno en la prueba que lo esta usando.

Por otro lado solo lo recomiendo cuando el trait es muy sencillo y no requerimos muchas llamadas a métodos internos de la clase que usaría el Trait en condiciones normales. Por ejemplo que tengamos que acceder a varios método del query builder de algún modelo, ya que tendrías que implementar estos métodos en la prueba.

Conclusión

Es obvio que tener pruebas solo para el trait puede no ser practico, sobre todo si ya lo probaste por medio de la clase que usa el Trait. Pero es buena idea tener en cuenta estas opciones si en el futuro el trait crece y se tiene que convertir en algo mas.

¿te gusto el articulo?

Recuerda dejar tu comentario y compartir este post!