Symfony 6: PHPUnit testowanie MVC z CRUD

Wstęp

Artykuł odwołuje się do:
Symfony 6 współpraca z bazą danych: tworzenie tabeli i wypełnienie jej randomowymi danymi
Symfony 6 budowanie aplikacji MVC: Wyświetlanie, dodawanie, edycja i usuwanie rekordów z wykorzystaniem Bootstrap 5.3

W poprzednich dwóch artykułach stworzyliśmy aplikację MVC w Symfony 6, która pozwala na tworzenie, wyświetlanie, edytowanie i usuwanie rekordów z tabeli. Przy implementacji funkcjonalności skorzystaliśmy z mechanizmów dostarczanych przez framework, takich jak komponenty, kontrolery, formularze czy routery. W tym artykule postaramy się zapewnić jakość naszej aplikacji, tworząc testy do wszystkich tras, które posiada. Wykorzystamy do tego PHPUnit – popularną bibliotekę do testowania jednostkowego aplikacji napisanych w języku PHP.

Wprowadzenie do PHPUnit

PHPUnit to narzędzie, które pozwala na sprawne tworzenie i wykonywanie testów jednostkowych. Stosując testy jednostkowe, będziemy w stanie sprawdzić, czy poszczególne fragmenty kodu naszej aplikacji działają zgodnie z oczekiwaniami. W rezultacie, dzięki testom, będziemy mogli szybciej wykrywać błędy oraz upewnić się, że wprowadzone zmiany nie wpłynęły negatywnie na działanie aplikacji.

Przygotowanie do testowania aplikacji Symfony

W trakcie tworzenia testów, będziemy korzystać z Symfony PHPUnit Bridge, który pozwala na łatwe tworzenie testów dla aplikacji Symfony. Ten pakiet dostarcza wiele przydatnych funkcji, takich jak WebTestCase, które pozwala na testowanie aplikacji MVC w sposób zbliżony do tego, jak użytkownik korzysta z aplikacji poprzez przeglądarkę internetową.

Celem tego artykułu jest przedstawienie procesu tworzenia testów dla naszej aplikacji, dzięki czemu zyskamy pewność, że wszystkie elementy aplikacji działają poprawnie i są zgodne z oczekiwaniami. Zaprezentujemy testy sprawdzające różne aspekty aplikacji, takie jak generowanie losowych osób, wyświetlanie listy osób, tworzenie, edycja oraz usuwanie osób.

Przegląd testów aplikacji

Testy aplikacji MVC z operacjami CRUD to kluczowy element w procesie tworzenia oprogramowania. Dzięki testom możemy mieć pewność, że nasza aplikacja działa poprawnie, nawet po wprowadzeniu zmian w kodzie. Poniżej przedstawiamy krótki opis poszczególnych testów, które sprawdzają różne aspekty aplikacji.

  1. Test testGenerateRandomPersons sprawdza, czy funkcja generowania losowych osób działa poprawnie. W pierwszej kolejności test kasuje dane z tabeli, dodaje jeden rekord, a następnie uruchamia trasę generującą losowe osoby. Po zakończeniu generacji, test sprawdza, czy w bazie danych znajduje się dokładnie 100 rekordów, oraz czy oryginalny rekord został usunięty.
  2. Test testIndex ma na celu sprawdzenie, czy strona główna z listą osób działa poprawnie. Test pobiera dane z bazy danych i porównuje je z tymi wyświetlanymi na stronie. Sprawdza również, czy liczba wierszy tabeli zgadza się z liczbą rekordów w bazie danych.
  3. Test testCreate weryfikuje, czy tworzenie nowych osób w aplikacji przebiega bezproblemowo. Test wypełnia formularz danymi, wysyła go, a następnie sprawdza, czy przekierowanie nastąpiło do ścieżki '/person’. Po przekierowaniu, test weryfikuje, czy nowa osoba została dodana do listy oraz czy rekord został dodany do bazy danych.
  4. Test testEdit sprawdza, czy edycja danych istniejącej osoby działa prawidłowo. Test tworzy nową osobę, zapisuje ją w bazie danych, a następnie przechodzi na stronę edycji osoby. Po zaktualizowaniu danych i przesłaniu formularza, test sprawdza, czy zmienione dane są widoczne na liście osób.
  5. Test testDelete ma na celu sprawdzenie, czy usuwanie osób z aplikacji przebiega poprawnie. Test tworzy nową osobę, zapisuje ją w bazie danych, a następnie przechodzi na stronę główną. Po wysłaniu żądania usunięcia osoby, test sprawdza, czy przekierowanie nastąpiło do strony głównej oraz czy usunięta osoba nie znajduje się już na liście osób.

Kod testu i jego wykonanie

Wszystkie te testy gwarantują, że aplikacja MVC z operacjami CRUD działa zgodnie z oczekiwaniami, co pozwala na lepsze zrozumienie kodu i łatwiejsze wprowadzanie zmian.

Poniżej przedstawiony jest cały kod testu, ważniejsze fragmenty kodu poprzedzone są komentarzami dla lepszego zrozumienia.

Utwórz plik tests/Controller/PersonControllerTest.php z następującą zawartością:

<?php

namespace App\Tests\Controller;

use App\Entity\Person;
use App\Repository\PersonRepository;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class PersonControllerTest extends WebTestCase
{
    public function testGenerateRandomPersons(): void
    {
        $client = static::createClient();

        // Wyczyść tabelę i dodaj jeden rekord
        $person = new Person();
        $person->setName('John');
        $person->setSurname('Doe');
        $person->setAge(30);
        $person->setEmail('john.doe@example.com');

        $entityManager = self::$kernel->getContainer()->get('doctrine.orm.entity_manager');
        $entityManager->getConnection()->executeStatement('TRUNCATE TABLE person');
        $entityManager->persist($person);
        $entityManager->flush();

        // Sprawdź, czy w bazie danych jest tylko jeden rekord
        $repo = $entityManager->getRepository(Person::class);
        $this->assertEquals(1, count($repo->findAll()));

        // Uruchom trasę
        $client->request('GET', '/generate-random-persons');
        $this->assertResponseIsSuccessful();

        // Sprawdź, czy w bazie danych jest dokładnie 100 rekordów
        $this->assertEquals(100, count($repo->findAll()));

        // Sprawdź, czy oryginalny rekord został usunięty
        $this->assertEmpty($repo->findOneBy(['name' => 'John', 'surname' => 'Doe']));
    }

    public function testIndex(): void
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/person');

        // Pobierz dane z bazy danych
        $personRepository = $this->getPersonRepository();
        $people = $personRepository->findAll();

        // Sprawdź, czy liczba wierszy tabeli jest taka sama jak liczba osób
        $tableRows = $crawler->filter('table tbody tr');
        $this->assertCount(count($people), $tableRows);

        // Sprawdź, czy dane z bazy danych zgadzają się z tymi wyświetlanymi w widoku
        foreach ($people as $index => $person) {
            /** @var Person $person */
            $this->assertStringContainsString($person->getName(), $tableRows->eq($index)->text());
            $this->assertStringContainsString($person->getSurname(), $tableRows->eq($index)->text());
            $this->assertStringContainsString((string)$person->getAge(), $tableRows->eq($index)->text());
            $this->assertStringContainsString($person->getEmail(), $tableRows->eq($index)->text());
        }
    }

    public function testCreate(): void
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/person/create');

        $form = $crawler->filter('form')->form();

        // Wypełnij formularz danymi
        $form['form[name]'] = 'John';
        $form['form[surname]'] = 'Doe';
        $form['form[age]'] = 30;
        $form['form[email]'] = 'john.doe@example.com';

        // Wyślij formularz
        $client->submit($form);

        // Upewnij się, że przekierowanie nastąpiło do ścieżki '/person'
        $this->assertTrue($client->getResponse()->isRedirect('/person'));

        // Śledź przekierowanie i sprawdź, czy nowa osoba została dodana do listy
        $crawler = $client->followRedirect();

        $this->assertSelectorTextContains('table tbody tr:last-child', 'John');
        $this->assertSelectorTextContains('table tbody tr:last-child', 'Doe');
        $this->assertSelectorTextContains('table tbody tr:last-child', '30');
        $this->assertSelectorTextContains('table tbody tr:last-child', 'john.doe@example.com');

        // Upewnij się, że rekord został dodany do bazy danych
        $personRepository = $this->getPersonRepository();
        $person = $personRepository->findOneBy(['email' => 'john.doe@example.com']);

        $this->assertNotNull($person);
        $this->assertSame('John', $person->getName());
        $this->assertSame('Doe', $person->getSurname());
        $this->assertSame(30, $person->getAge());
        $this->assertSame('john.doe@example.com', $person->getEmail());
    }

    public function testEdit(): void
    {
        $client = static::createClient();

        // Stwórz nową osobę
        $person = new Person();
        $person->setName('John');
        $person->setSurname('Doe');
        $person->setAge(30);
        $person->setEmail('john.doe@example.com');

        // Zapisz osobę do bazy danych
        $entityManager = self::$kernel->getContainer()->get('doctrine.orm.entity_manager');
        $entityManager->persist($person);
        $entityManager->flush();

        // Pobierz id stworzonej osoby
        $personId = $person->getId();

        // Przejdź do strony edycji osoby
        $crawler = $client->request('GET', "/person/{$personId}/edit");

        // Wyszukaj formularz po selektorze CSS, na przykład po znaczniku 'form'
        $form = $crawler->filter('form')->form();

        // Zaktualizuj dane osoby
        $form['form[name]'] = 'Jane';
        $form['form[surname]'] = 'Doe';
        $form['form[age]'] = 25;
        $form['form[email]'] = 'jane.doe@example.com';

        // Wyślij formularz
        $client->submit($form);

        // Sprawdź, czy przekierowano do strony głównej
        $this->assertTrue($client->getResponse()->isRedirect('/person'));

        // Przejdź na stronę główną
        $crawler = $client->followRedirect();

        // Sprawdź, czy zmienione dane są widoczne na liście osób
        $this->assertStringContainsString('Jane', $crawler->filter('table')->text());
        $this->assertStringContainsString('Doe', $crawler->filter('table')->text());
        $this->assertStringContainsString('25', $crawler->filter('table')->text());
        $this->assertStringContainsString('jane.doe@example.com', $crawler->filter('table')->text());
    }

    public function testDelete(): void
    {
        $client = static::createClient();

        // Stwórz nową osobę
        $person = new Person();
        $person->setName('John');
        $person->setSurname('Doe');
        $person->setAge(30);
        $person->setEmail('john.doe@example.com');

        // Zapisz osobę do bazy danych
        $entityManager = self::$kernel->getContainer()->get('doctrine.orm.entity_manager');
        $entityManager->persist($person);
        $entityManager->flush();

        // Pobierz id stworzonej osoby
        $personId = $person->getId();

        // Przejdź na stronę główną
        $crawler = $client->request('GET', '/person');

        // Sprawdź, czy osoba znajduje się na liście
        $this->assertStringContainsString('John', $crawler->filter('table')->text());

        // Prześlij żądanie usunięcia osoby
        $client->request('POST', "/person/{$personId}/delete");

        // Sprawdź, czy przekierowano do strony głównej
        $this->assertTrue($client->getResponse()->isRedirect('/person'));

        // Przejdź na stronę główną
        $crawler = $client->followRedirect();

        // Sprawdź, czy link do edycji dla usuniętej osoby nie istnieje
        $editLinks = $crawler->filter("a[href='". $client->getContainer()->get('router')->generate('app_person_edit', ['id' => $personId]) ."']");

        $this->assertEquals(0, $editLinks->count());

        // Jeśli istnieje link do edycji, sprawdź, czy nie zawiera danych usuniętej osoby
        if ($editLinks->count() > 0) {
            $this->assertStringNotContainsString('John', $editLinks->text());
            $this->assertStringNotContainsString('Doe', $editLinks->text());
            $this->assertStringNotContainsString('30', $editLinks->text());
            $this->assertStringNotContainsString('john.doe@example.com', $editLinks->text());
        }
    }

    private function getPersonRepository(): PersonRepository
    {
        // Uruchamia kernel (jądro aplikacji), aby uzyskać dostęp do kontenera zależności
        self::bootKernel();
        // Pobiera kontener zależności i korzysta z Doctrine, aby pobrać repozytorium dla encji Person
        return self::$kernel->getContainer()->get('doctrine')->getRepository(Person::class);
    }
}

Teraz należy wykonać test wpisując w terminal polecenie:

php bin/phpunit tests/Controller/PersonControllerTest.php

Jeśli testy przejdą, otrzymamy taki komunikat:

Wszystkie testy PHPUnit przeszły

Podsumowanie

W tym artykule pokazany został sposób tworzenia testów dla aplikacji MVC w Symfony 6 z operacjami CRUD. Testy te pozwolą nam sprawdzić poprawność działania aplikacji oraz ułatwią utrzymanie i rozwój naszego projektu. Wykonanie testów po każdej zmianie w kodzie daje nam pewność, że wprowadzone modyfikacje nie wpłynęły negatywnie na funkcjonalność aplikacji.

Dzięki tym testom możemy szybciej i bezpieczniej wprowadzać zmiany, a także uniknąć wprowadzenia błędów. Symfony 6 oferuje wiele narzędzi do testowania, które sprawiają, że praca nad projektem staje się bardziej efektywna. Pamiętaj o tworzeniu testów dla swojej aplikacji, aby zapewnić jej wyższy poziom jakości i niezawodności.

Cały kod aplikacji z testami dostępny jest na GitHubie.

Leave a Comment

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Scroll to Top