Applicatietests met Laravel 5.4: Laravel Dusk

DoorPeter Bond, MSc

In Laravel 5.1 werd een API toegevoegd aan de Laravel Core waarmee applicatietests kinderspel werden. Daar waar unit tests een geïsoleerd stukje code (in de regel functies) testen op correct gedrag, toetst een applicatietest het gedrag van de applicatie als geheel.

Met de meegeleverde command-line interface Artisan kan de blauwdruk van zo’n applicatietest gegenereerd worden met het commando php artisan make:test FooTest. In het mapje tests verschijnt vervolgens een klasse FooTest waar je mee uit de voeten kunt.

Stel je hebt een resource genaamd transactions en je resource controller heeft een show-functie waarmee je een transactie kunt ophalen en bekijken in je browser. Het somt dan bijvoorbeeld het transactienummer op en een bedrag. Met een dergelijke applicatietest valt eenvoudig te testen of dit ook echt gebeurt. Nadat je met Artisan de test-klasse hebt gegenereerd kun je een functie schrijven genaamd testShow(). Vervolgens laat je deze functie twee dingen doen. Eerst laat je hem navigeren naar de juiste route door een transactie op te vragen, en vervolgens controleer je of inderdaad het transactienummer (tx_id) en het bedrag (amount) worden weergegeven. Bijvoorbeeld:

public function testShow()
{
    $transaction = App\Models\Transaction::inRandomOrder()->first();
    $this->visitRoute('transaction.show', ['id' => $transaction->id])
        ->see($transaction->tx_id)
        ->see($transaction->amount);
}

En dat is alles. Je weet nu in één klap of je route werkt, of de transactie uit de database kan worden gevist, en of enkele eigenschappen van deze transactie ook geretourneerd worden.

Had je een applicatie waarbij ingelogd moest worden? Geen probleem. Haal een gebruiker op uit de database die bij de resource kan, en door te starten met de actingAs-functie was dat ook geregeld:

$user = App\Models\User::inRandomOrder()->first();
$this->actingAs($user)
    ->visitRoute(...)

Verder zijn er nog tal van mogelijkheden om betrekkelijk eenvoudig je applicatietests te schrijven. Zo kun je eenvoudig HTML-formulieren manipuleren, je gebruikte middleware uitschakelen (handig voor bijvoorbeeld het testen van je APIs, waarbij je zo de authenticatie-middleware kunt uitschakelen), enz.

Een probleem is echter dat de HTML-respons niet wordt gerenderd. Wanneer je een visit-actie uitvoert wordt simpelweg een webaanroep naar je Laravel-applicatie nagebootst en handelt hij deze zoals gewoonlijk af. Uiteindelijk resulteert dit in een instantie van Illuminate\Http\Response die normaal gesproken in je browser wordt weergegeven, maar nu wordt het opgeslagen in $this->response voor je applicatietest om je asserties op los te laten.

Dit heeft als nadeel dat je dus niet je JavaScript op deze manier kunt testen. Heb je veel interactieve JavaScript-afhankelijke pagina’s, dan heb je dus een beetje pech. Met Laravel 5.4 is dit nu gelukkig verleden tijd. Met de komst van Laravel Dusk kun je namelijk echt de gehele pagina, inclusief je JavaScript, testen alsof je zelf in een browser aan het werk bent.

Applicatietests met Laravel Dusk: gebruik van Chrome

First things first. Als je Laravel 5.3 naar 5.4 update, gaan de applicatietests, zoals hierboven beschreven, kapot. Je zult eerst de BrowserKit package moeten installeren en wat kleine wijzigingen moeten uitvoeren als je deze wilt behouden. De package met instructies is hier te vinden: https://github.com/laravel/browser-kit-testing.

Zodra je dat achter de rug hebt kun je nieuwe tests gaan toevoegen die gebruik maken van Laravel Dusk. Dusk maakt gebruik van de ChromeDriver en voert je tests dan ook letterlijk uit in Chrome. (Het staat je overigens vrij om Dusk andere browsers te laten gebruiken.)

De blauwdruk voor een Dusk-test kan ook weer automatisch aangemaakt worden met Artisan met het commando php artisan dusk:make FooTest. Deze testklasse komt vervolgens terecht in het mapje tests/Browser. In dit mapje zie je ook een ExampleTest.php staan. Als je deze opent valt op dat tests nu een namespace hebben gekregen, dit was voorheen niet het geval.

Wat verder opvalt aan ExampleTest is dat de test in een Closure is opgenomen binnen de browse-functiecontext:

$this->browse(function ($browser) {
    $browser->visit('/')
            ->assertSee('Laravel');
});

In die closure kun je de gangbare functies gewoon nog aanroepen (al is onder meer de see-functie hernoemd, naar assertSee). Om de Dusk-tests vervolgens te draaien kun je het commando php artisan dusk gebruiken.

Interactie met de pagina met Laravel Dusk

Doordat nu de betreffende pagina echt in Chrome geladen wordt, kun je nu met alle elementen interacteren zoals JavaScript dat ook zou kunnen. En dat niet alleen, je kunt hiervoor zelfs jQuery-selectoren gebruiken.

Als je zou willen simuleren dat je een bepaald input-veld met de id foo invult, dan zou dit als volgt gaan:
$browser->value(‘#foo’, ‘de input’);
Als dit als gevolg zou hebben dat bepaalde toeters en bellen zouden moeten afgaan door een stukje JavaScript, kun je een assertie, die je daarna zou willen laten draaien, laten wachten op het verschijnen van een nieuw stukje DOM.

Denk bijvoorbeeld aan een contactformulier waarbij je expres een foutieve waarde invult, waardoor je JavaScript-validatie een foutmelding weergeeft. Dit zou als volgt eruit kunnen gaan zien:

$browser->value('#telephone', 'fout');
$browser->whenAvailable('.alert-danger'), function($errorMessage) {
    $errorMessage->assertSee('Vul a.u.b. een correct telefoonnummer in');
});

De whenAvailable-functie wacht 5 seconden op het geselecteerde element. Als deze tijd is verstreken, wordt er een exception gegooid. Wanneer het element wel verschijnt, wordt deze meegegeven aan de closure. In dit voorbeeld hebben we gekeken of de foutmelding de juiste tekst bevat, na het invullen van een ongeldig telefoonnummer.

Als je iets simpels zoals bovenstaande wilt doen, namelijk evalueren of een stukje tekst verschijnt, kun je ook makkelijk gebruik maken van de waitForText-functie:
$browser->waitForText(‘Vul a.u.b. een correct telefoonnummer in’);

Ook kun je eenvoudig meerdere browsers aanmaken voor je test. Dit doe je door extra parameters mee te geven aan de browse-closure, bijvoorbeeld:

$this->browse(function ($first, $second) {
    (...)
});

Zowel $first als $second kun je nu gebruiken als browser om asserties op uit te voeren. Dit is handig voor situaties er op enige manier interactie plaatsvindt tussen twee personen via de browser. Denk bijvoorbeeld aan een ingelogde helpdeskmedewerker die chatondersteuning kan bieden aan een bezoeker op de website:

$this->browse(function ($first, $second) {
    $first->loginAs(User::find(1))
        ->visitRoute('chatSupport')
        ->waitForText('New client connected')
        ->type('message', 'Hoe kan ik u van dienst zijn?')
    
    $second->visit('/')
        ->waitForText('Hoe kan ik u van dienst zijn?')
        ->type('message', 'Hoi, ik heb een probleem.')
        ->press('Send');
    
    $first->waitForText('Nieuw bericht')
        ->assertSee('Hoi ik heb een probleem.');
});

Merk ook op dat actingAs is hernoemd naar loginAs.

Verder kun je met Dusk nu zelfs muisacties simuleren. Zoals een mouse-over ($browser->mouseover(‘.selector’)) of het klikken op een DOM-element ($browser->click(‘.selector’)). Je kunt zelfs elementen heen-en-weer ‘slepen’ ($browser->drag(‘.from-selector’, ‘.to-selector’)).

Samenvattend kun je met Laravel Dusk de handelingen van je eindgebruikers héél goed nabootsen. Hierdoor kun je nog breder je applicatie testen en ook nauwkeuriger dan hiervoor. En je kunt natuurlijk ook als vanouds met de BrowserKit tests blijven uitvoeren, die op veel fronten perfect voldoen.

Over de auteur

Peter Bond, MSc

Oprichter van Bond IT Solutions en software-ingenieur.

Laat een reactie achter