Cum testăm o aplicație Laravel?

Uitându-mă pe Laracast (și în multe dintre materialele despre acest subiect), observ că ceea ce numesc ei unit test este, mai mult, test de integrare.

De exemplu, testul ăsta nu mi se pare că ar fi unitar deloc, plecând de la niște premise:

  • că există un sistem de autentificare
  • că există modelul Thread
  • că există ruta /subscriptions
  • etc.
        $this->signIn();
        // Given we have a thread...
        $thread = create('App\Thread');
        // And the user subscribes to the thread...
        $this->post($thread->path() . '/subscriptions');
        $this->assertCount(1, $thread->fresh()->subscriptions);

Eu am învățat că testele ar trebui rulate în izolare și, implicit, fără să se atingă de resurse externe (db, file system, http etc). Ori lumea Laravel vine să-mi dea peste cap aceste cunoștințe. :confused:

Pe de altă parte, am încercat să testez un controller, făcând mock la dependențele externe (e.g. modele). Având modele cu relații, mocking-ul a devenit extrem de rapid overkill (și sunt abia la primul test!):

        $CallProcessorStub = $this->createMock(CallProcessor::class);

        $account = $this->createMock(Account::class);

        $trackingNumber =  Mockery::mock(TrackingNumber::class)->makePartial();
        $trackingNumber->shouldReceive('getAttribute')
            ->with('account')
            ->andReturn($account);

        $CallProcessorStub->method('getTrackingNumber')
            ->willReturn($trackingNumber);

Iar acum stau și ma întreb: am așteptările greșite de la un unit test?

1 Like

Ai perfecta dreptate. Alea sunt teste functionale. Testele unitare testeaza o singura metoda din clasa. Fiecare are rostul lui.
Daca alegi sa faci testare unitara, testezi doar metoda curenta si atat. Restul tot e un mock. Ceea ce e super okay, insa daca ai multe chestii, devine enervant.
In anumite circumstante e de preferat, totusi sa faci test functional si sa nu faci mock dupa tot, ci sa folosesti clasele din proiect, daca instantierea lor nu e foarte costisitoare.
Pe Laracast genul asta de teste apar in contextul TDD, iar acolo in general se incepe cu un test functional care esueaza si teoretic, repari fiecare eroare care apare pana il faci sa treaca. Asta sa te forteze sa-ti construiesti toata arhitectura cu rute si clase alaturate si ce-ti mai trebuie.

Controller ar trebui sa fie cat mai scurt si logica mutata in business/domain classes. Pentru aia poti face unit testing ok si in cazul controllerului mai important ar fi testul de integrare/e2e cu rendering-ul de html.

Un dezavantaj pe care-l ai daca folosesti implementarea concreta a claselor este ca iti pot pica testele daca schimbi implementarea claselor dependente, motiv pentru care testul e de fapt unul functional.
Asta poate sa fie un lucru bun sau rau… depinde de ce doresti. Probabil ca ai teste si pentru clasele de care depinzi, iar cand schimbi ceva pe-acolo, iti pica mai multe teste decat ar pica in mod normal, daca esti axat pe puritatea testelor unitare. Oricum, cand pica trebuie sa le repari.
Daca clasele de care depinzi au efecte secundare, din nou e o idee proasta sa ai implementare concreta, pentru ca risti sa ai teste care uneori merg si uneori pica, in functie de un state extern, iar asta e o situatie pe care vrei sa o eviti.

Edit:
M-am uitat la exemplul pe care l-ai dat. Testul pe care l-ai exemplificat e test functional. Il are in folder-ul feature. Alea sunt toate teste functionale. Testele unitare sunt in folder-ul unit.

Din cate stiu modul de testare al lui Jeffrey si Adam Wathan este acela de a porni de la testele de acceptanta si apoi a cobori la nivel de unit test.

Vezi un exemplu aici.