Nie mogę napisać testu do metody wywołującej metodę statyczną

14-09-2022

Jakiś czas temu podczas konwersacji, mój rozmówca zauważył trudność jaką jest napisanie testu do metody, która wywołuje metody statyczne. Początkowo uznałam, że nie znam odpowiedzi, jak do tego podejść. Jednakże wieczorem „lampka się zapaliła” i olśniło mnie. Przecież to jest dokładnie taki sam problem jak z napisaniem testu do metody, która w sobie tworzy nowe obiekty!

Sytuację z tworzeniem nowych obiektów opisałam tutaj: „Nie mogę napisać testu do metody – part 1„.

Moja metoda ma odwołania do metod statycznych

Spójrzmy więc na poniższy przykład:

class Legacy
{
    public function __construct(private UsersRepository $repository)
    {
    }

    public function addUser(string $login, string $password): bool
    {
        // ...

        if (!$this->repository->addUser($login, $password)) {
            Sentry::catchException(new \RuntimeException(sprintf("Unable to add user %s", $login)));

            return false;
        }

        return true;
    }

    // ...
}

 

Jaki mamy tutaj problem?

  1. Nie możemy napisać testów jednostkowych do metody addUser, ponieważ wywołuje ona metodę statyczną, która wysyła powiadomienie do Sentry.
  2. Wywołanie jest statyczne, w związku z czym przekazanie do konstruktora może być problematyczne. Należałoby zrobić większą refaktoryzację (modyfikacja kodu, bez modyfikacji funkcjonalności).
  3. Klasy wykorzystujące klasę Legacy również nie mają napisanych testów.

Jaki problem chcemy rozwiązać?

  1. Chcemy napisać testy do metody addUser, bez modyfikacji metody statycznej.

Jak możemy ten problem rozwiązać?

  1. Wydzielam wywołanie metody statycznej do osobnej metody catchException o modyfikatorze dostępu protected.
    class Legacy
    {
        public function __construct(private UsersRepository $repository)
        {
        }
    
        public function addUser(string $login, string $password): bool
        {
            // ...
    
            if (!$this->repository->addUser($login, $password)) {
                $this->catchException(new \RuntimeException(sprintf("Unable to add user %s", $login)));
                return false;
            }
    
            return true;
        }
    
        // ...
    
        protected function catchException(\Throwable $exception): void
        {
            Sentry::catchException($exception);
        }
    }
  2. W teście tworzę klasę anonimową rozszerzającą Legacy i nadpisuję metodę catchException. Przy takiej modyfikacji możemy już napisać testy do naszej metody 😉

    use PHPUnit\Framework\TestCase;
    
    class LegacyTest extends TestCase
    {
        public function testHappyPathOfAddingUser(): void
        {
            $repository = $this->createMock(UsersRepository::class);
            $legacy = new class ($repository) extends Legacy {
                protected function catchException(\Throwable $exception): void
                {
                    // do nothing
                }
            };
            $repository
                ->expects($this->once())
                ->method('addUser')
                ->willReturn(false);
            $this->assertFalse($legacy->addUser("test", "passWORD123!@#"));
        }
    
        // .... inne testy
    }
    

Ćwiczenia

Chcesz poćwiczyć to podejście? Przygotowałam dla Ciebie 1 ćwiczenie z przykładowymi klasami do modyfikacji 🙂

Link do kodu

Tutaj mamy klasę ManageUser i w niej metodę addUser. Znajdują się tutaj 2 wywołania metod statycznych. W ramach ćwiczenia zmodyfikuj tą klasę, tak aby wywołania statycznie nie odbywały się bezpośrednio w metodzie addUser, a następnie napisz testy do metody addUser.

Podsumowanie

Spodobał Ci się ten artykuł? Podziel się nim! Masz jakieś pytania, a może jakieś inne możliwości podejścia do problemu? Zapraszam do dyskusji! 😉

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

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.