Probando Controladores Laravel

Probar los controladores no es lo más fácil del mundo. Bueno, déjame reformular eso: probarlos es muy fácil; Lo que es difícil, al menos al principio, es determinante. qué Probar.

¿Debería un controlador verificar el texto en la página? ¿Debe tocar la base de datos? ¿Debería asegurarse de que existen variables en la vista? Si este es tu primer viaje en heno, ¡estas cosas pueden ser confusas! Déjame ayudar.

Las pruebas del controlador deben verificar las respuestas, asegurarse de que se activen los métodos de acceso a la base de datos correctos y afirmar que las variables de instancia adecuadas se envían a la vista.

El proceso de prueba de un controlador se puede dividir en tres partes.

  • Aislar: Se burlan de todas las dependencias (tal vez excluyendo el Ver).
  • Llamada: Activar el método de control deseado..
  • Asegurar: Realizar afirmaciones, verificando que el escenario se ha configurado correctamente..

El Hello World of Controller Testing

La mejor manera de aprender estas cosas es a través de ejemplos. Aquí esta la "Hola Mundo"de controlador de pruebas en Laravel.

 cliente-> solicitud ('GET', 'mensajes'); 

Laravel aprovecha un puñado de componentes de Symfony para facilitar el proceso de prueba de rutas y vistas, incluyendo HttpKernel, DomCrawler y BrowserKit. Es por esto que es de suma importancia que sus pruebas de PHPUnit se hereden, no PHPUnit \ _Framework \ _TestCase, pero Caso de prueba. No se preocupe, Laravel aún extiende el primero, pero ayuda a configurar la aplicación Laravel para realizar pruebas, además de proporcionar una variedad de métodos de afirmación de ayuda que se recomienda utilizar. Más sobre eso en breve.

En el fragmento de código de arriba, hacemos un OBTENER solicitud de / mensajes, o localhost: 8000 / mensajes. Suponiendo que esta línea se agregue a una nueva instalación de Laravel, Symfony lanzará un NotFoundHttpException. Si trabaja a lo largo, pruébelo corriendo phpunit desde la linea de comando.

 $ phpunit 1) PostsControllerTest :: testIndex Symfony \ Component \ HttpKernel \ Exception \ NotFoundHttpException:

En lenguaje humano, esto se traduce esencialmente a "Hey, traté de llamar a esa ruta, pero no tienes nada registrado, tonto!"

Como puede imaginar, este tipo de solicitud es lo suficientemente común como para que tenga sentido proporcionar un método auxiliar, como $ this-> call (). De hecho, ¡Laravel hace eso mismo! Esto significa que el ejemplo anterior puede ser refactorizado, así:

 # app / tests / controllers / PostsControllerTest.php función pública testIndex () $ this-> call ('GET', 'posts'); 

La sobrecarga es tu amigo

Aunque nos atendremos a la funcionalidad básica en este capítulo, en mis proyectos personales, llevo un paso más allá al permitir métodos tales como $ esto-> obtener (), $ esto-> publicar (), Gracias a la sobrecarga de PHP, esto solo requiere la adición de un método único, que puede agregar a app / tests / TestCase.php.

 # app / tests / TestCase.php función pública __call ($ method, $ args) if (in_array ($ method, ['get', 'post', 'put', 'patch', 'delete']))  devuelve $ this-> call ($ method, $ args [0]);  lanzar nueva BadMethodCallException; 

Ahora, eres libre de escribir $ this-> get ('posts') y lograr exactamente el mismo resultado que los dos ejemplos anteriores. Sin embargo, como se indicó anteriormente, mantengamos la funcionalidad básica del marco para simplificar.

Para hacer que la prueba pase, solo necesitamos preparar la ruta adecuada.

  

Corriendo phpunit De nuevo nos volverá a verde.


Las aserciones del ayudante de Laravel

Una prueba en la que se encontrará escribiendo varias veces es una que garantiza que un controlador pasa una variable particular a una vista. Por ejemplo, el índice método de PostsController debe pasar un $ mensajes Variable a su vista asociada, ¿verdad? De esa manera, la vista se puede filtrar a través de todas las publicaciones y mostrarlas en la página. Esta es una prueba importante para escribir.!

Si es una tarea tan común, entonces, una vez más, ¿no tendría sentido para Laravel proporcionar una afirmación de ayudante para lograr esto mismo? Por supuesto que lo haría. Y, por supuesto, Laravel lo hace.!

Illuminate \ Foundation \ Testing \ TestCase incluye una serie de métodos que reducirán drásticamente la cantidad de código necesario para realizar afirmaciones básicas. Esta lista incluye:

  • assertViewHas
  • assertResponseOk
  • assertRedirectedTo
  • assertRedirectedToRoute
  • assertRedirectedToAction
  • assertSessionHas
  • assertSessionHasErrors

Los siguientes ejemplos llaman GET / mensajes y verifica que sus vistas reciban la variable., $ mensajes.

 # app / tests / controllers / PostsControllerTest.php función pública testIndex () $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('posts'); 

Propina: Cuando se trata de formatear, prefiero proporcionar un salto de línea entre la afirmación de una prueba y el código que prepara la etapa.

assertViewHas es simplemente un poco de azúcar que inspecciona el objeto de respuesta, que se devuelve de $ this-> call () - y verifica que los datos asociados a la vista contienen una puestos variable.

Al inspeccionar el objeto de respuesta, tiene dos opciones de núcleo.

  • $ respuesta-> getOriginalContent (): Recupera el contenido original, o lo devuelto. Ver. Opcionalmente, puede acceder a la original propiedad directamente, en lugar de llamar a la obtener contenido original método.
  • $ respuesta-> getContent (): Recupera la salida renderizada. Si un Ver La instancia se devuelve desde la ruta, entonces obtener el contenido() será igual a la salida HTML. Esto puede ser útil para las verificaciones de DOM, como "la vista debe contener esta cadena."

Supongamos que el puestos ruta consiste en:

  

Deberiamos correr phpunit, Se graznará con un útil próximo paso mensaje:

 1) PostsControllerTest :: testIndex Falló al afirmar que una matriz tiene la clave 'posts'.

Para hacerlo verde, simplemente buscamos las publicaciones y las pasamos a la vista..

 # app / route.php Route :: get ('posts', function () $ posts = Post :: all (); return View :: make ('posts.index', ['posts', $ posts]) ;);

Una cosa que hay que tener en cuenta es que, como el código actual se mantiene, solo garantiza que la variable, $ mensajes, Se pasa a la vista. No inspecciona su valor. los assertViewHas acepta opcionalmente un segundo argumento para verificar el valor de la variable, así como su existencia.

 # app / tests / controllers / PostsControllerTest.php función pública testIndex () $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('posts', 'foo'); 

Con este código modificado, hasta que la vista tiene una variable, $ mensajes, eso es igual a foo, la prueba fallará En esta situación, sin embargo, es probable que prefiramos no especificar un valor, sino declarar que el valor es una instancia de Laravel. Illuminate \ Database \ Eloquent \ Collection clase. ¿Cómo podríamos lograr eso? PHPUnit proporciona una ayuda assertInstanceOf afirmación para llenar esta misma necesidad!

 # app / tests / controllers / PostsControllerTest.php función pública testIndex () $ response = $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('posts'); // getData () devuelve todos los vars adjuntos a la respuesta. $ posts = $ response-> original-> getData () ['posts']; $ this-> assertInstanceOf ('Illuminate \ Database \ Eloquent \ Collection', $ posts); 

Con esta modificación, hemos declarado que el controlador debe pasar $ mensajes - una instancia de Illuminate \ Database \ Eloquent \ Collection - a la vista. Excelente.


Burlándose de la base de datos

Hay un problema evidente con nuestras pruebas hasta ahora. Lo atrapaste?

Para cada prueba, se está ejecutando una consulta SQL en la base de datos. Aunque esto es útil para ciertos tipos de pruebas (aceptación, integración), para las pruebas básicas del controlador, solo servirá para disminuir el rendimiento.

He perforado esto en tu cráneo varias veces en este punto. No estamos interesados ​​en probar la capacidad de Eloquent para obtener registros de una base de datos. Tiene sus propias pruebas. Taylor sabe que funciona! No perdamos tiempo y potencia de procesamiento repitiendo esas mismas pruebas..

En su lugar, es mejor burlarse de la base de datos y simplemente verificar que los métodos apropiados sean llamados con los argumentos correctos. O, en otras palabras, queremos asegurarnos de que Publicar :: todos () Nunca dispara y golpea la base de datos. Sabemos que funciona, por lo que no requiere pruebas..

Esta sección dependerá en gran medida de la biblioteca de Mockery. Por favor revise ese capítulo de mi libro, si todavía no está familiarizado con él..

Refactorización Requerida

Desafortunadamente, hasta ahora, hemos estructurado el código de una manera que hace que sea prácticamente imposible de probar.

 # app / route.php Route :: get ('posts', function () // Ouch. ¡No podemos probar esto! $ posts = Post :: all (); return View :: make ('posts. índice ') -> con (' posts ', $ posts););

Esta es precisamente la razón por la que se considera una mala práctica anidar llamadas Elocuentes en sus controladores. No confunda las fachadas de Laravel, que son comprobables y se pueden intercambiar con mockes (Queue :: shouldReceive ()), con tus modelos elocuentes. La solución es inyectar la capa de base de datos en el controlador a través del constructor. Esto requiere un poco de refactorización..

Advertencia: Almacenar la lógica dentro de las devoluciones de llamadas de ruta es útil para proyectos pequeños y API, pero hacen que las pruebas sean increíblemente difíciles. Para aplicaciones de cualquier tamaño considerable, use controladores.

Registremos un nuevo recurso reemplazando el puestos ruta con:

 # app / route.php Route :: resource ('posts', 'PostsController');

… Y crea el controlador de recursos necesario con Artisan.

 $ php artisan controller: haga que PostsController Controller se haya creado con éxito!

Ahora, en lugar de hacer referencia a la Enviar Modelar directamente, lo inyectaremos en el constructor del controlador. Aquí hay un ejemplo condensado que omite todos los métodos de descanso, excepto el que actualmente estamos interesados ​​en probar..

 post = $ post;  índice de función pública () $ posts = $ this-> post-> all (); return View :: make ('posts.index') -> con ('posts', $ posts); 

Tenga en cuenta que es una mejor idea escribir una interfaz en lugar de hacer referencia al propio modelo de Eloquent. Pero, una cosa a la vez! Vamos a trabajar para eso.

Esta es una forma significativamente mejor de estructurar el código. Debido a que el modelo ahora está inyectado, tenemos la capacidad de intercambiarlo con una versión simulada para realizar pruebas. Aquí hay un ejemplo de cómo hacer eso:

 mock = Mockery :: mock ('Eloquent', 'Post');  public function tearDown () Mockery :: close ();  función pública testIndex () $ this-> mock -> shouldReceive ('all') -> once () -> andReturn ('foo'); $ this-> app-> instance ('Publicar', $ this-> simulacro); $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('posts'); 

El beneficio clave de esta reestructuración es que, ahora, la base de datos nunca se verá afectada innecesariamente. En su lugar, utilizando Mockery, simplemente verificamos que la todos El método se activa en el modelo..

 $ this-> mock-> shouldReceive ('all') -> once ();

Desafortunadamente, si elige renunciar a la codificación en una interfaz, y en su lugar inyectar Enviar modelo en el controlador, se debe utilizar un poco de artimañas para evitar el uso de las estáticas de Eloquent, que puede chocar con Mockery. Es por esto que secuestramos tanto el Enviar y Elocuente Clases dentro del constructor de la prueba, antes de que se hayan cargado las versiones oficiales. De esta manera, tenemos una pizarra limpia para declarar cualquier expectativa. El inconveniente, por supuesto, es que no podemos utilizar de forma predeterminada ningún método existente, mediante el uso de métodos de burla, como makePartial ().

El contenedor IoC

El contenedor de IoC de Laravel facilita drásticamente el proceso de inyectar dependencias en sus clases. Cada vez que se solicita un controlador, se resuelve fuera del contenedor IoC. Como tal, cuando tenemos que declarar que una versión simulada de Enviar debe usarse para pruebas, solo necesitamos proporcionar a Laravel la instancia de Enviar que deberia ser usado.

 $ this-> app-> instance ('Publicar', $ this-> simulacro);

Piense en este código como diciendo "Hola Laravel, cuando necesites una instancia de Enviar, Quiero que uses mi versión simulada.."Porque la aplicación extiende el Envase, Tenemos acceso a todos los métodos de IoC directamente fuera de ella.

Al crear una instancia del controlador, Laravel aprovecha el poder de la reflexión de PHP para leer el tipo e insinuar la dependencia para usted. Está bien; no tienes que escribir un solo enlace para permitir esto; esta automatizado!


Redirecciones

Otra expectativa común de que se encontrará escribiendo es que garantiza que el usuario sea redirigido a la ubicación adecuada, tal vez al agregar una nueva publicación a la base de datos. ¿Cómo podríamos lograr esto??

 # app / tests / controllers / PostsControllerTest.php función pública testStore () $ this-> mock -> shouldReceive ('create') -> once (); $ this-> app-> instance ('Publicar', $ this-> simulacro); $ this-> call ('POST', 'posts'); $ this-> assertRedirectedToRoute ('posts.index'); 

Suponiendo que estamos siguiendo un sabor relajante, para agregar una nueva publicación, deberíamos ENVIAR a la colección, o puestos (no confundas la ENVIAR Método de solicitud con el nombre del recurso, que por casualidad tiene el mismo nombre).

 $ this-> call ('POST', 'posts');

Entonces, solo necesitamos aprovechar otra de las aserciones del ayudante de Laravel, assertRedirectedToRoute.

Propina: Cuando un recurso se registra con Laravel (Ruta :: recurso ()), el marco registrará automáticamente las rutas nombradas necesarias. correr rutas artesanales php Si alguna vez olvidas cuales son estos nombres.

Es posible que prefiera asegurarse también de que $ _POST superglobal se pasa a la crear método. A pesar de que no estamos enviando un formulario físicamente, todavía podemos permitirlo, a través de Entrada :: reemplazar () Método, que nos permite "apilar" esta matriz. Aquí está la prueba modificada, que utiliza Mockery con() Método para verificar los argumentos pasados ​​al método al que hace referencia debería recibir.

 # app / tests / controllers / PostsControllerTest.php función pública testStore () Input :: replace ($ input = ['title' => 'My Title']);

$ this-> mock-> shouldReceive ('create') -> once () -> with ($ input); $ this-> app-> instance ('Publicar', $ this-> simulacro); $ this-> call ('POST', 'posts'); $ this-> assertRedirectedToRoute ('posts.index');

Caminos

Una cosa que no hemos considerado en esta prueba es la validación. Debe haber dos caminos separados a través de la almacenar Método, dependiendo de si la validación pasa:

  1. Redirigir nuevamente al formulario "Crear publicación" y mostrar los errores de validación del formulario.
  2. Redirigir a la colección, o la ruta nombrada, posts.index.

Como práctica recomendada, cada prueba debe representar solo una ruta a través de su código.

Este primer camino será por validación fallida..

 # app / tests / controllers / PostsControllerTest.php función pública testStoreFails () // Establecer etapa para una validación fallida Input :: replace (['title' => "]); $ this-> app-> instance ('Post ', $ this-> mock); $ this-> call (' POST ',' posts '); // La validación fallida debe volver a cargar el formulario de creación $ this-> assertRedirectedToRoute (' posts.create '); // Los errores debe enviarse a la vista $ this-> assertSessionHasErrors (['title']);

El fragmento de código anterior declara explícitamente qué errores deberían existir. Alternativamente, puede omitir el argumento para assertSessionHasErrors, en cuyo caso, simplemente verificará que se ha enviado un paquete de mensajes (en la traducción, su Redirección incluye withErrors ($ errores)).

Ahora para la prueba que maneja la validación exitosa..

 # app / tests / controllers / PostsControllerTest.php función pública testStoreSuccess () // Establecer el escenario para una validación exitosa Input :: replace (['title' => 'Foo Title']);

$ this-> mock-> shouldReceive ('create') -> once (); $ this-> app-> instance ('Publicar', $ this-> simulacro); $ this-> call ('POST', 'posts'); // Debería redirigirse a la colección, con un mensaje flash de éxito $ this-> assertRedirectedToRoute ('posts.index', ['flash']);

El código de producción para estas dos pruebas podría ser:

 # app / controllers / PostsController.php public function store () $ input = Input :: all (); // Ejecutaremos la validación en el controlador para mayor comodidad // Debería exportarlo al modelo, o un servicio $ v = Validator :: make ($ input, ['title' => 'required']); if ($ v-> fail ()) return Redirect :: route ('posts.create') -> withInput () -> withErrors ($ v-> messages ());  $ this-> post-> create ($ input); return Redirect :: route ('posts.index') -> con ('flash', '¡Tu publicación ha sido creada!'); 

Observe cómo Validador ¿Está anidado directamente en el controlador? En general, le recomiendo que lo abstraiga para un servicio. De esa manera, puede probar su validación en forma aislada de cualquier controlador o ruta. No obstante, dejemos las cosas como son por simplicidad. Una cosa a tener en cuenta es que no nos estamos burlando de la Validador, aunque ciertamente podrías hacerlo. Debido a que esta clase es una fachada, se puede cambiar fácilmente con una versión simulada, a través de la fachada. debería recibir Método, sin que tengamos que preocuparnos por inyectar una instancia a través del constructor. Ganar!

 # app / controllers / PostsController.php Validator :: shouldReceive ('make') -> once () -> andReturn (Mockery :: mock (['fail' => 'true']));

De vez en cuando, encontrará que un método que debe ser burlado debe devolver un objeto, en sí mismo. Por suerte, con Mockery, esto es pan comido: solo necesitamos crear un simulacro anónimo y pasar una matriz, que señala el nombre del método y el valor de respuesta, respectivamente. Como tal:

 Mockery :: mock (['fail' => 'true'])

preparará un objeto, que contiene un falla () método que devuelve cierto.


Repositorios

Para permitir una flexibilidad óptima, en lugar de crear un enlace directo entre su controlador y un ORM, como Eloquent, es mejor codificar a una interfaz. La ventaja considerable de este enfoque es que, en caso de que necesite cambiar Eloquent por, digamos, Mongo o Redis, hacerlo literalmente requiere la modificación de una sola línea. Aún mejor, el controlador nunca necesita ser tocado.

Los repositorios representan la capa de acceso a datos de su aplicación.

¿Qué podría una interfaz para gestionar la capa de base de datos de un Enviar ¿parece? Esto debería empezar.

  

Esto puede ser extendido, pero hemos agregado los métodos mínimos para la demostración: todos, encontrar, y crear. Observe que las interfaces del repositorio se almacenan dentro de app / repositorios. Debido a que esta carpeta no se carga automáticamente de manera predeterminada, necesitamos actualizar la compositor.json archivo para la aplicación para referenciarlo.

 // composer.json "autoload": "classmap": [// ... "app / repositories"]

Cuando se agrega una nueva clase a este directorio, no olvide compositor dump-autoload -o. los -o, (optimizar) la bandera es opcional, pero siempre debe usarse, como una buena práctica.

Si intenta inyectar esta interfaz en su controlador, Laravel se ajustará a usted. Adelante; pruébalo y verás. Aquí está la modificada PostController, que se ha actualizado para inyectar una interfaz, en lugar de la Enviar Modelo elocuente.

 post = $ post;  índice de función pública () $ posts = $ this-> post-> all (); return View :: make ('posts.index', ['posts' => $ posts]); 

Si ejecuta el servidor y ve el resultado, se encontrará con la temida (pero hermosa) página de error de Whoops, declarando que "PostRepositoryInterface no es instanciable."


Si lo piensas, por supuesto, ¡el marco está haciendo graznidos! Laravel es inteligente, pero no es un lector mental. Debe indicarse qué implementación de la interfaz debe usarse dentro del controlador.

Por ahora, vamos a añadir este enlace a app / route.php. Más tarde, utilizaremos proveedores de servicios para almacenar este tipo de lógica..

 # app / route.php App :: bind ('Repositories \ PostRepositoryInterface', 'Repositories \ EloquentPostRepository');

Verbalizar esta función llamada como, "Laravel, bebé, cuando necesitas una instancia de PostRepositoryInterface, Quiero que uses EloquentPostRepository."

aplicación / repositorios / EloquentPostRepository simplemente será una envoltura alrededor de Eloquent que implementa PostRepositoryInterface. De esta manera, no estamos restringiendo la API (y cualquier otra implementación) a la interpretación de Eloquent; Podemos nombrar los métodos como deseamos.

  

Algunos podrían argumentar que la Enviar El modelo debe ser inyectado en esta implementación para propósitos de prueba. Si está de acuerdo, simplemente inyecte a través del constructor, como de costumbre..

Eso es todo lo que debe tomar! Actualiza el navegador, y las cosas deberían volver a la normalidad. Solo que, ahora, su aplicación está mucho mejor estructurada y el controlador ya no está vinculado a Eloquent.

Imaginemos que, dentro de unos meses, su jefe le informará que necesita intercambiar Eloquent con Redis. Bien, porque ha estructurado su aplicación de esta manera preparada para el futuro, solo necesita crear el nuevo aplicación / repositorios / RedisPostRepository implementación:

  

Y actualizar el enlace:

 # app / route.php App :: bind ('Repositories \ PostRepositoryInterface', 'Repositories \ RedisPostRepository');

Al instante, ahora estás aprovechando Redis en tu controlador. Date cuenta cómo app / controllers / PostsController.php nunca fue tocado? Esa es la belleza de eso.!


Estructura

Hasta ahora, en esta lección, nuestra organización ha sido un poco deficiente. Enlaces IoC en el rutas.php ¿expediente? ¿Todos los repositorios agrupados en un directorio? Claro, eso puede funcionar al principio, pero, muy rápidamente, se hará evidente que esto no se escala..

En la sección final de este artículo, haremos un PSR de nuestro código y aprovecharemos a los proveedores de servicios para registrar cualquier enlace aplicable.

PSR-0 define los requisitos obligatorios que deben cumplirse para la interoperabilidad del autocargador.

Un cargador PSR-0 puede registrarse con Composer, a través de psr-0 objeto.

 // composer.json "autoload": "psr-0": "Way": "app / lib /"

La sintaxis puede ser confusa al principio. Ciertamente fue para mí. Una manera fácil de descifrar "Way": "app / lib /" es pensar para ti mismo "La carpeta base para el Camino espacio de nombres se encuentra en app / lib."Por supuesto, reemplace mi apellido con el nombre de su proyecto. La estructura de directorios para que coincida con esto sería:

  • app /
    • lib /
    • Camino/

A continuación, en lugar de agrupar todos los repositorios en una repositorios directorio, un enfoque más elegante podría ser clasificarlos en múltiples directorios, como por ejemplo:

  • app /
    • lib /
    • Camino/
      • Almacenamiento/
      • Enviar/
        • PostRepositoryInterface.php
        • EloquentPostRepository.php

Es vital que cumplamos con esta convención de nomenclatura y carpeta, si queremos que la carga automática funcione como se espera. Lo único que queda por hacer es actualizar los espacios de nombres para PostRepositoryInterface y EloquentPostRepository.

  

Y para la implementación:

  

Aquí vamos; eso es mucho mas limpio Pero ¿qué pasa con esos molestos enlaces? El archivo de rutas puede ser un lugar conveniente para experimentar, pero tiene poco sentido almacenarlos allí permanentemente. En su lugar, utilizaremos proveedores de servicios..

Los proveedores de servicios no son más que clases de arranque que pueden usarse para hacer lo que deseen: registrar un enlace, enganchar un evento, importar un archivo de rutas, etc..

Un proveedor de servicios registro() Será activado automáticamente por Laravel..

 app-> bind ('Way \ Storage \ Post \ PostRepositoryInterface', 'Way \ Storage \ Post \ EloquentPostRepository'); 

Para dar a conocer este archivo a Laravel, solo necesita incluirlo en app / config / app.php, dentro de proveedores formación.

 # app / config / app.php 'suppliers' => array ('Illuminate \ Foundation \ Providers \ ArtisanServiceProvider', 'Illuminate \ Auth \ AuthServiceProvider', // ... 'Way \ Storage \ StorageServiceProvider')

Bueno; Ahora tenemos un archivo dedicado para registrar nuevos enlaces..

Actualización de las pruebas

Con nuestra nueva estructura en su lugar, en lugar de burlarnos del modelo Eloquent, podemos burlarnos de él. PostRepositoryInterface. Aquí hay un ejemplo de una tal prueba:

 # app / tests / controllers / PostsControllerTest.php función pública testIndex () $ mock = Mockery :: mock ('Way \ Storage \ Post \ PostRepositoryInterface'); $ mock-> shouldReceive ('all') -> once (); $ this-> app-> instance ('Way \ Storage \ Post \ PostRepositoryInterface', $ mock); $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('posts'); 

Sin embargo, podemos mejorar esto. Es lógico que cada método dentro de PostsControllerTest Requerirá una versión simulada del repositorio. Como tal, es mejor extraer parte de este trabajo de preparación en su propio método, así:

 # app / tests / controllers / PostsControllerTest.php función pública setUp () parent :: setUp (); $ this-> mock ('Way \ Storage \ Post \ PostRepositoryInterface');  función pública simulada ($ clase) $ mock = Mockery :: mock ($ clase); $ this-> app-> instance ($ class, $ mock); devuelve $ simulacro;  función pública testIndex () $ this-> mock-> shouldReceive ('all') -> once (); $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('posts'); 

No esta mal ay?

Ahora, si desea ser súper-volador y está dispuesto a agregar un toque de lógica de prueba a su código de producción, ¡incluso podría realizar su burla dentro del modelo Eloquent! Esto permitiría:

 Post :: shouldReceive ('all') -> once ();

Detrás de las escenas, esto se burlaría. PostRepositoryInterface, y actualizar el enlace IoC. No se puede obtener mucho más legible que eso!

Permitir esta sintaxis solo requiere que actualices la Enviar modelo, o, mejor, una Modelo base Que se extienden todos los modelos elocuentes. Aquí hay un ejemplo de lo anterior: