Back to Blogs
Blog Img

​End-to-end testen van een real-time chatapplicatie met Cypress

Cypress, zo vinden veel experts, haalt voor een groot deel de pijn uit testen. Gleb Bahmutov, een ‘JavaScript-ninja’ die in zijn vorige functie meegeholpen heeft aan de ontwikkeling van Cypress, gaf een webinar over het end-to-end testen van een real-time webapplicatie voor chat met deze JavaScript-tool voor frontend-testing. Een samenvatting.

Webapplicatie op basis van Socket.io

Hoe kun je realistische end-to-end Cypress-tests voor een real-time chatapplicatie uitvoeren? Die vraag staat centraal tijdens de online lezing van Gleb Bahmutov. Deze ‘JavaScript-ninja’ is tegenwoordig senior director of engineering bij Mercari US, was voorheen VP Engineering bij Cypress en heeft in zijn vrije tijd al meer dan 500 blogposts over softwareontwikkeling geschreven.

In het webinar, waarvan de code-heavy slides hier zijn te bekijken, behandelt hij vier verschillende punten die allemaal een verschillend perspectief op je code geven: mock app code, mock socket connection, open second socket connection en het draaien van twee testrunners. De applicatie die Bahmutov bespreekt, is een webapplicatie voor chat op basis van Socket.io. De code van de applicatie heeft hij op GitHub gezet.

Werkt de applicatie?

Allereerst werpt Bahmutov de vraag op: werkt de applicatie? “Je kunt hem wel openen, maar dingen gaan gemakkelijk kapot. In werkelijkheid moet je daarom sowieso testen en dat kun je maar beter geautomatiseerd doen. Daarom gaan we vandaag een Cypress-test schrijven.” De JS-goeroe legt uit dat een typische end-to-end test normaliter het volgende patroon volgt: command – assertion(s) – command – assertion(s).

Door naar de eerste test. “In Cypress zie je echt alles, bijvoorbeeld hoe de applicatie eruitziet in de echte browser, de gebruikersnaam, et cetera. Om de vraag ‘werkt de app echt?’ te kunnen beantwoorden, moeten we onszelf eerst afvragen of een tweede gebruiker die deelneemt aan de chat mijn berichten kan zien en omgekeerd. Immers: misschien spiegelt de applicatie de berichten, maar verstuurt ‘ie ze niet.”

Testassistenten

Bahmutov maakt een klein zijsprongetje: hij legt uit dat het ‘probleem’ met dit soort testassistenten is dat ze ergens een grens moeten trekken: welk deel testen we wel en niet? Hij vervolgt: “Vanuit Cypress kunnen we verbinden met een server van Socket.io en handelen als een tweede gebruiker. Een normale test wordt uitgevoerd op de pagina, een ander deel van de code gaat naar de socket-server en gedraagt zich als een tweede gebruiker. Wat ook kan: nog een Cypress-testrunner openen en twee browsers met elkaar laten praten.”

Stub applicatiecode

“We beginnen met iets simpels: stub applicatiecode. Je kunt een class of object in de applicatiecode creëren, dat applicatieobject kun je blootstellen aan de test. Vanuit de test kun je dan het applicatieobject bespioneren en binnenkomende berichten testen.” Bahmutov laat zien dat de app UI-berichten toont die binnenkomen in de applicatiecode, de pagina toont dan de berichten.

Mock websocket

Er is nu bevestigd dat de applicatiecode werkt tot in de socket, alleen zouden de socket commands fout kunnen zijn. “Om dit te testen, kun je een mock websocket gebruiken. Daarvoor moet je de prod websocket vervangen voor de mock socket.” Bahmutov wijst erop dat de intercept-functionaliteit van Cypress hier van pas komt. Vervolgens is het zaak de mock socket te injecteren in de iFrame van de applicatie. “We hebben nu geverifieerd dat de socket API binnen de applicatie werkt. Oftewel de UI-code, de intermediaire code en de websocket.”

Testen of de server werkt

De socket API binnen de applicatie kan dan wel werken, het zou kunnen dat de server kapot is. “Tijdens de test willen we de pagina controleren, door de UI gaan en de tweede gebruiker verbinden en simuleren, die terugpraat en naar de berichten ‘luistert’ door te verbinden met dezelfde server.” Bahmutov legt uit hoe dit in zijn werk gaat en komt met een tip: hanteer voor de tweede gebruiker een verbinding van buiten de browserpagina, aangezien dat een cleanere manier is om een tweede aparte verbinding te maken met de server.

Twee Cypress-instanties tegelijkertijd draaien

Hoe draai je twee Cypress-instanties naast elkaar? Daarvoor heb je twee aparte specs nodig, die allebei strikt via de UI van de pagina opereren. Waarschijnlijk moet je hiervoor, zo legt de testexpert uit, aparte Cypress-configuratiebestanden creëren aangezien je twee instanties tegelijk draait. Ook moet de NPM-module concurrently worden geïnstalleerd.

Deze methode heeft volgens Bahmutov op het eerste gezicht een aantal tekortkomingen. Zo wachten bijvoorbeeld de testinstanties van Cypress niet echt op de ander, ze zijn min of meer blind voor elkaar. Je wilt dus kunnen beheren hoe de twee testrunners de test draaien. Om de testrunners te synchroniseren, kun je een aparte Socket.io creëren. Daarbij hoeft je slechts twee commands te implementeren: checkpoint en wait for checkpoint. Vervolgens moet je de server van Socket.io synchroniseren en de plugin file van Cypress implementeren.

De eerste en de tweede testrunner

Bahmutov legt uit dat de eerste testrunner de pagina bezoekt, de naam instelt en de server checkt, waarna de eerste gebruiker deelneemt aan de chat. Nu moet de tweede gebruiker toegevoegd worden aan de chat. Er wordt dan gewacht voor het checkpoint dat controleert of dat de tweede gebruiker is toegevoegd, waarna in de eerste testrunner wordt nagegaan of dat de tweede gebruiker er daadwerkelijk is. Die moet dan een bericht posten, waardoor je kunt zien of dat het bericht zichtbaar is.

In de tweede spec file staat dat de eerste gebruiker eerst het checkpoint moet bereiken. Daarna wordt geverifieerd dat het bericht van de eerste gebruiker komt, de eerste testrunner wacht ondertussen op dit checkpoint. Zo ziet, in een notendop, de eerste testronde eruit. Bahmutov: “Oftewel: eerst start hij, dan wacht hij, vervolgens komen de berichten uit de tweede testronde, hij wordt aangesloten, hij wacht en dan wordt snel het bericht gecommuniceerd. Bij de tweede testronde gaat het veel sneller: hij wordt aangesloten, checkpoint, bericht gezien en klaar. De twee testrunners communiceren met en wachten altijd op elkaar om te zorgen dat alles in de goede volgorde gaat.”

Conclusie: wat is beter?

“Wat is nou beter?”, besluit Bahmutov zijn webinar. “Ik heb vier verschillende manieren laten zien om code te testen: stub app code, websocket mocken zodat je nooit een server van Socket.io hoeft te draaien, handelen als een tweede gebruiker via een verbinding met Socket.io die afgezonderd is van de pagina en tot slot twee testrunners naast elkaar draaien, waarbij ook wordt gehandeld als een tweede gebruiker. In mijn optiek is handelen als een tweede gebruiker, zonder de volledige testrunner te starten, veel beter. Dat is namelijk een volledige end-to-end test.”

“De tweede browser communiceert precies zoals wij zouden communiceren via een verbinding met Socket.io. We communiceren alleen via een openbare API en hebben niks geprivilegieerds gedaan, aangezien dit ook niet noodzakelijk was.” Bovendien is de test nog steeds heel snel, oordeelt Bahmutov. “Minder dan twee seconde om te verbinden aan beide kanten, de pagina te laden, een paar berichten uit te wisselen en ten slotte te bevestigen dat de socket-server werkt, de pagina wordt getoond en dat de pagina dingen verzendt. Vandaar dat dit mijn favoriete manier is om real-time chatapplicaties te testen.”

Meer lezen?

Wil je dieper in de materie duiken? Bahmutov heeft drie blogposts geschreven die gedetailleerd op deze stof ingaan: Test a socket.io chat app using Cypress, Run two Cypress test runners at the same time en Sync two Cypress runners via checkpoints.