Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Configuración y desmontaje

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

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

Login Subscribe

Sigamos refactorizando nuestra prueba. En el método de prueba, creamos un MockResponse,MockHttpClient, e instanciamos GitHubService con un simulacro de LoggerInterface. Estamos haciendo lo mismo en esta prueba de arriba. ¿No dijo Ryan que había que DRY out nuestro código en otro tutorial? Bien... Supongo que le haremos caso.

Empieza añadiendo tres propiedades private a nuestra clase: unaLoggerInterface $mockLogger, seguida de MockHttpClient $mockHttpClient y finalmente MockResponse $mockresponse. En la parte inferior de la prueba, crea unprivate function createGithubService() que requiera array $responseData y que devuelva GithubService. En el interior, di $this->mockResponse = new MockResponse() que json_encode()'s el $responseData.

Como crearemos el MockResponse después de instanciar el MockHttpClient, que verás en un segundo, necesitamos pasar nuestra respuesta al cliente sin utilizar el constructor del cliente. Para ello, podemos decir$this->mockHttpClient->setResponseFactory($this->mockResponse). Finalmente devuelve un new GithubService() con $this->mockHttpClient y $this->mockLogger.

Podríamos utilizar un constructor para instanciar nuestros mocks y establecerlos en esas propiedades, pero PHPUnit sólo instanciará nuestra clase de prueba una vez, sin importar cuántos métodos de prueba tenga. Y queremos asegurarnos de que tenemos objetos simulados nuevos para cada prueba que se ejecute. ¿Cómo podemos hacerlo? En la parte superior, añade protected function setUp(). En el interior, di $this->mockLogger = $this->createMock(LoggerInterface::class) y luego$this->mockHttpClient = new MockHttpClient().

Abajo, en el método de prueba, corta la matriz de respuesta, luego di$service = $this->createGithubService() y pega la matriz.

Veamos cómo van nuestras pruebas en el terminal...

./vendor/bin/phpunit

Y... ¡Ya! ¡Todo va bien!

La idea es bastante sencilla: si tu clase de prueba tiene un método llamado setUp(), PHPUnit lo llamará antes de cada método de prueba, lo que nos proporciona mocks frescos al comienzo de cada prueba. ¿Necesitas hacer algo después de cada prueba? Lo mismo: crea un método llamado tearDown(). Esto no es tan común... pero podrías hacerlo si quieres limpiar algunos cambios en el sistema de archivos que se hicieron durante la prueba. En nuestro caso, no es necesario.

Además de setUp() y tearDown(), PHPUnit también tiene algunos otros métodos, comosetUpBeforeClass() y tearDownAfterClass(). Estos se llaman una vez por clase, y hablaremos de ellos a medida que sean relevantes en futuros tutoriales. Y si te lo estás preguntando, estos métodos se llaman "Métodos de Fijación" porque ayudan a configurar cualquier "fijación" para poner tu entorno en un estado conocido para tu prueba.

En cualquier caso, volvamos a la refactorización. Para la primera prueba de esta clase, recorta la matriz de respuesta, selecciona todo este "código muerto", añade$service = $this->createGithubService() y luego pega la matriz. Podemos eliminar la variable $service a continuación. Pero ahora tenemos que averiguar cómo mantener estas expectativas que estábamos utilizando en el antiguo $mockHttpClient. Poder comprobar que sólo llamamos a GitHub una vez con el método HTTP GET y que estamos utilizando la URL correcta, es bastante valioso.

Afortunadamente, esas clases simuladas tienen un código especial sólo para esto. A continuación,assertSame() que 1 es idéntico a $this->mockHttpClient->getRequestCount()luego assertSame() que GET es idéntico a $this->mockResponse->getRequestMethod(). Finalmente, copia y pega la URL en assertSame() y llama a getRequestUrl() enmockResponse. Elimina la antigua $mockHttpClient... y las declaraciones de use que ya no utilizamos arriba.

Muy bien, es hora de comprobar las vallas...

./vendor/bin/phpunit

Y... ¡Vaya! ¡Todo sigue en verde!

Bueno, ahí lo tienes... Hemos ayudado a Bob a mejorar Dinotopia añadiendo algunas pequeñas funciones a la aplicación. Pero, lo que es más importante, hemos podido comprobar que esas funciones funcionan como pretendíamos. ¿Hay más trabajo por hacer? Por supuesto, vamos a llevar nuestra aplicación al siguiente nivel añadiendo una capa de persistencia para almacenar los dinos en la base de datos y aprender a escribir pruebas para ello. Estas pruebas, en las que utilizas conexiones reales a la base de datos o realizas llamadas reales a la API, en lugar de burlas, se denominan a veces pruebas de integración. Ese es el tema del próximo tutorial de esta serie.

Espero que hayas disfrutado de tu estancia en el parque, y gracias por mantener tus brazos y piernas dentro del vehículo en todo momento. Si tienes alguna pregunta o sugerencia, o quieres ir con Big Eaty en el Jeep, déjanos un comentario. Muy bien, ¡nos vemos en el próximo episodio!

Leave a comment!

6
Login or Register to join the conversation
yaroslavche Avatar
yaroslavche Avatar yaroslavche | posted hace 6 meses

I really enjoyed this course, many thanks. I found a lot of interesting practices that I will definitely use in the future. Looking forward to the next parts.
I have only one question (just kidding): why can dino length be zero or negative? They deserve to have an assert for that! =)

Reply
Jesse-Rushlow Avatar
Jesse-Rushlow Avatar Jesse-Rushlow | SFCASTS | yaroslavche | posted hace 6 meses

Howdy!

I'm glad that you not only enjoyed the new series, but it was also helpful to you! That's music to our ears...

why can dino length be zero or negative? They deserve to have an assert for that! =)

This is actually a very good question! If PHP had native support for unsigned integers we could prevent a dino's length being "-100" or something silly like that. But, in all honesty, this isn't a use case I'd be too worried about. There's a balance between writing tests for every possible scenario, keeping your tests maintainable, and being practical. If the dino's size was used to determine if they could be let loose in the petting zoo, we'd probably would have a test to keep Big Eaty out of there... :)

Thank you for the feedback. We appreciate it!

Reply
yaroslavche Avatar

I agree about balance, but let me argue a little =) Since Dinosaur entity have only one place to change length (the constructor), then we can add a condition there, which would check length and otherwise throws an exception. Obviously, dino length can't be negative or zero on instantiation. Then in \App\Tests\Unit\Entity\DinosuarTest::testDinosaurHasCorrectSizeDescriptionFromLength we can expect that exception, when data provider yields 0 or negative number. In that case, we can be sure, that each dino has a valid size. But, also it requires several changes in tests (instantiation with length or set non-negative default size), since right now default size is 0.

Reply
Jesse-Rushlow Avatar
Jesse-Rushlow Avatar Jesse-Rushlow | SFCASTS | yaroslavche | posted hace 6 meses

That is definitely one way to do it, and probably how I would tackle it if it were a big enough concern of mine. But, again it's all about prioritizing area of concerns. If the app is only instantiating Dinosaur's without using unsafe input, say from a form, then ya - I'm not going to bother... But, if we did something like that in episode 3, who knows what might happen...

1 Reply
Ek24 Avatar

Great and good to follow tutorial ... i hope bob is happy.

Cant wait for part II

Reply
Jesse-Rushlow Avatar
Jesse-Rushlow Avatar Jesse-Rushlow | SFCASTS | Ek24 | posted hace 7 meses

Awesome! I'm glad you enjoyed it. Episode 2 is in the works now and we can't wait to release it. Stay Tuned...

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "symfony/asset": "6.1.*", // v6.1.0
        "symfony/console": "6.1.*", // v6.1.4
        "symfony/dotenv": "6.1.*", // v6.1.0
        "symfony/flex": "^2", // v2.2.3
        "symfony/framework-bundle": "6.1.*", // v6.1.4
        "symfony/http-client": "6.1.*", // v6.1.4
        "symfony/runtime": "6.1.*", // v6.1.3
        "symfony/twig-bundle": "6.1.*", // v6.1.1
        "symfony/yaml": "6.1.*" // v6.1.4
    },
    "require-dev": {
        "phpunit/phpunit": "^9.5", // 9.5.23
        "symfony/browser-kit": "6.1.*", // v6.1.3
        "symfony/css-selector": "6.1.*", // v6.1.3
        "symfony/phpunit-bridge": "^6.1" // v6.1.3
    }
}
userVoice