Intentare probar desde pruebas unitarias hasta las prueba integral
En esta primera parte veo la introducción a preparar el proyecto para Test y luego el enfoque de las pruebas será ir por cada capa por cada feature, en este caso el buscador de un producto, y probar cada capa: Repository, Domain, Controler y Vista.
Primero prueba del repositorio.
php artisan make:test ProductSearch/RepositoryProductSearchTest --unit
Para ejecutar específicamente la clase:
phpunit --filter RepositoryProductSearchTest
Ejecutar las migraciones y copiar en la base de datos “<nombre database>_test”
Para usar la base de datos, se tiene que crear un archivo de entorno para testing

El comando para actualizar el cache de testing es:
php artisan config:cache --env=testing
Se hace el setup para el test con los datos que se necesitarán
class RepositoryProductSearchTest extends TestCase { // trait para que los datos no afecten a la base de datos use DatabaseMigrations; //datos que se especificaran necesarios para las pruebas private $nameCategoriaPrueba = "CategoriaPrueba"; //id resultante para comparar en las pruebas private $idCategory = 0; public function setUp(): void { //metodo padre de setup parent::setUp(); //asi se llama a un factory, util para crear data $newCategory = factory(Category::class)->create([ //el array sirve para especificar algunos datos Category::NAME=>$this->nameCategoriaPrueba ]); //resto de codigo... $this->idCategory = $newCategory->id; dd($newCategory);
Comando para crear el factory
php artisan make:factory CategoryFactory --model Category
Una forma de crear el factory seria:
<?php use Faker\Generator as Faker; $factory->define(App\User::class, function (Faker $faker) { $user = new \App\Models\User(); $user->name = $faker->name; $user->email = $faker->email; $user->rol = $faker->numberBetween(1,4); $user->password = bcrypt($faker->password); $user->estado = \ConstantesUserEstado::HABILITADO; return $user->toArray(); });
En caso tenga relación a otra tabla, se puede llamar al factory de cada modelo:
<?php $factory->define(Product::class, function (Faker $faker) { $newProduct = new Product(); $newProduct->name=$faker->name; .... //relations $newProduct->Market_Seller_users_id= factory(User::class)->create()->id; $newProduct->Market_Seller_id=factory(Seller::class)->create([ Seller::USERS_ID=>$newProduct->Market_Seller_users_id ])->id; $newProduct->Market_id = factory(Market::class)->create([ Market::SELLER_ID=>$newProduct->Market_Seller_id, Market::SELLER_USERS_ID=>$newProduct->Market_Seller_users_id ])->id; return $newProduct->toArray(); });
En como se definió puede ocurrir un comportamiento no esperado, al momento de llamar a este factory ya se crea la instancia de Seller, y si se sobrescribe las propiedades, el Seller ya fue creado, para que no lo cree hasta que se termine de definir los parámetros, se puede definir como function:
<?php use Faker\Generator as Faker; use App\Models\ProductPresentation; use App\Models\Product; $factory->define(ProductPresentation::class, function (Faker $faker) { /* ANTES $newProductPresentation = new ProductPresentation(); $newProductPresentation->media_type = "jpg"; $newProductPresentation->file_url=$faker->imageUrl(); $newProductPresentation->Product_id=factory(Product::class)->create()->id; $newProductPresentation->is_principal=$faker->boolean; $newProductPresentation->orden=$faker->numberBetween(); $newProductPresentation->name_ref=$faker->name; return $newProductPresentation->toArray(); */ return [ ProductPresentation::MEDIA_TYPE=>ConstantesProductPresentationMediaType::IMG, ProductPresentation::FILE_URL=>$faker->imageUrl(), ProductPresentation::PRODUCT_ID=>function () { return Product::factory()->create()->id; }, ProductPresentation::IS_PRINCIPAL=>$faker->boolean, ProductPresentation::ORDEN=>$faker->numberBetween(), ProductPresentation::NAME_REF=>$faker->name ]; });
para ir probado el test, se puede llamar especificando la clase y evitar llamar todo
// Para ejecutar solo un test: phpunit --filter methodName path/to/file.php // Para ejecutar todos los tests de una clase: phpunit --filter RepositoryProductSearchTest
Antes de llamar la DB se tiene que crear la DB para test, se puede llenar con el comando de migrate mas el parámetro de env testing:
php artisan migrate --env=testing
Ahora el archivo repositorio quedaría :
La ubicación seria dentro de una carpeta por cada caso de uso

Describo cada parte del archivo
... /** ... * author twiter @JavierTwiteando * * (1) * phpunit --filter RepositoryProductSearchTest --colors */ class RepositoryProductSearchTest extends TestCase { // (2) trait para que los datos no afecten a la base de datos use DatabaseTransactions; //config //(3) private $sizePage = 3; ... private $productsName = [ "A", "B", "C", "D", "E", "F", "1", "2" ]; private $productsCreateAt = [ "12/09/2020", "12/10/2020", "12/11/2020", "12/12/2020", "12/01/2021", "12/02/2021", "12/03/2021", "12/04/2021", ]; ... public function setUp(): void { //metodo padre de setup parent::setUp(); //(4) ... $newCategories = factory(Category::class,$this->sizeCategories)->create(); $newUser = factory(User::class)->create([ User::ROL=>\ConstantesUserRol::SELLER ]); ... //(3) para las comprobaciones $this->categorys = $newCategories; $this->categoryEmpty = $newCategoryEmpty; } //(5) function getRepositoryProduct():RepositoryProduct{ return app(RepositoryProduct::class); } /** (6) * @test */ public function search_category_exist() { //(7) $repositoryProduct = $this->getRepositoryProduct() ; //llamar al repositorio con el filtro de la categoria $results=$repositoryProduct ->addFilterCategory($this->categorys[0]->id) ->setPaginateSize($this->sizePage) ->search(); $this->assertNotNull($results,'el resultado fue nulo'); $this->assertEquals($this->sizeProductsByCategory,$results->total()); $this->assertEquals($repositoryProduct->getPaginateSize(), sizeof( $results->items() )); } ... /** * phpunit --filter order_by_most_recents ./tests/Unit/ProductSearch/RepositoryProductSearchTest.php --colors * * @test */ public function order_by_most_recents() { $repositoryProduct = $this->getRepositoryProduct(); //llamar al repositorio con el filtro de la categoria $results=$repositoryProduct ->addFilterCategory($this->categorys[0]->id) ->orderBy(\ConstantesProductSearchOrderBy::MAS_NUEVOS) ->search(); $first_item = Product::cast($results->items()[0]); $second_item = Product::cast($results->items()[1]); $this->assertNotNull($results,"el resultado fue nulo"); $this->assertEquals($first_item->created_at, Carbon::parse($this->productsCreateAt[7])); $this->assertLessThan($first_item->created_at,$second_item->created_at); } ... }
(1) Anoto cada comando necesario para probar cada parte del archivo.
(2) Se usa el trait para no guardar los datos generados para la prueba en cada test.
(3) Se anota argumentos para la generación de la data y setear el estado que tendrá el sistema para las pruebas(tamaño de paginado, fechas de creación de productos, cantidad de categorías,etc) , también se declara variables donde se guardará los datos generados y luego ser comparados.
(4) Se crea lo datos según los argumentos del estado del sistema con los factorys sobrescribiendo los datos necesarios.
(5) Se crea una clase para obtener una instancia del repositorio para las pruebas.
(6) Se crea cada función para los test indicando la anotación @test para que lo reconozca como método para testing.
(7) Se obtiene el repositorio, se indica los datos del estado del sistema a probar y se verifica con lo assert que cumpla lo esperado.
Se corren los test hasta refactorizarlos y pasarlos, primero uno por uno

y luego todos

Listo, ahora toca probar el dominio en el siguiente post (aquí el link cuando este listo)
Referencias:
Video con un ejemplo de inyección de dependencias:
https://www.youtube.com/watch?v=uARTy_sJrIo
Opciones de formas de castear un objecto a un tipo de clase:
https://stackoverflow.com/a/54481076/15149489
Articulo clave para entender los mocks:
https://kordes.dev/posts/laravel-ioc-mocking-tests-practical-example
Factorys
https://laravel.com/docs/5.3/database-testing#factory-states