Mockování v Cypressu s NextJS

TL;DR
Článek vysvětluje, jak implementovat mock backend pro testování frontendu v aplikaci NextJS pomocí Cypressu. Pro zajištění konzistentního a efektivního testování tým zkoumal možnosti, jako je nasazení samostatné instance backendu nebo použití Dockeru, ale obojí mělo své nevýhody. Konečným řešením bylo vytvoření minimálního backendu v JavaScriptu v rámci prostředí Cypressu, který umožňuje testům zachytávat požadavky frontendu a vracet předdefinované odpovědi. Tento přístup zajišťuje snadné spouštění testů na lokálních strojích i v rámci CI/CD pipeline.

Abychom ověřili správnou funkcionalitu jednotlivých částí systému při implementaci Knížkomatu, bylo nezbytné náš kód otestovat. Pro backend jsme zvolili přístup přes integrační testy, které ověřovaly správnou funkčnost jednotlivých endpointů, o nich však tento článek nebude. Na frontendu jsme se rozhodli využít end-to-end testy uživatelského rozhraní, které procházejí vybrané scénáře v rámci aplikace, jako by se jednalo o běžného uživatele. Pro tyto frontendové testy jsme měli několik základních požadavků:

  1. musí být součástí projektu frontendu a psané v jazyce, který umí frontend vývojáři
  2. musí být spustitelné v rámci CICD, které nasazuje aplikaci na server
  3. musí být snadno spustitené na vývojových strojích programátorů

Všechny tyto požadavky splňuje knihovna Cypress, jelikož umožňuje psát testy v JavaScriptu, testy lze spustit přes NodeJS a funguje velice snadno na jakémkoli zařízení. Aby testy nebyly závislé na backendu a šlo snadno kontrolovat data, se kterými test pracuje, rozhodli jsme se, že budeme požadavky odcházející z frontendu nějakým způsobem mockovat a vracet předpřipravené odpovědi. Zde jsme však narazili na hlavní problém celého našeho plánu a tím je middleware psaný v NextJS.

Formulace problému

V hlavním článku o projektu Knížkomat jsme zmiňovali, že middleware mimojiné usnadňuje vytváření požadavků na frontendu. To je pravda, ale jelikož má middleware komunikaci s frontendem ve svojí režii, není tato komunikace tak srozumitelně definovaná, jako je komunikace mezi middleware a backendem, která se řídí RESTovým API definovaným na backendu. Pokud tedy v Cypressu chceme mockovat požadavky, je potřeba odchytávat zprávy bez předchozí znalosti toho, jak tyto požadavky vlastně vypadají. To sice není nereálný úkol, ale je rozhodně velmi náročný, jelikož je potřeba pro každý požadavek odchytávat všechny zprávy a následně ručně “luštit”, co každá zpráva znamená, v jakém je formátu a jakou očekává odpověď. To vše, když jen o krok dál (tedy mezi middleware a backendem) máme krásně nadefinovanou a lidsky čitelnou komunikaci.

Možná řešení

Prvním řešením, které nás napadlo, bylo “zabalit” do procesu v Cypressu i middleware tak, aby pak šlo odchytávat komunikaci na backend. Jelikož je ale middleware samostatný proces, Cypress s ním nechce mít nic společného a přestože ho spustí jako součást testu, tak ho není možné obalit do mockovacích funkcí. Napadlo nás tedy několik alternativních řešení. 

Jednou z variant by bylo nasadit samostatnou instanci backendu, která by sloužila pouze pro potřeby testů. Problémem tohoto přístupu je, že backend by byl perzistentní a pokud by některý test upravil data na backendu, tak při dalším spuštění testu by byla data už změněná, což porušuje zásadu konzistentnosti testů. I kdyby se konzistence dat nějak vyřešila, byl by na serveru spuštěná plnohodnotná instance backendu, která by většinu času vůbec nic nedělala, což není příliš efektivní.

Dalším možným řešením je spouštět backend v nějaké formě virtualizace, nejspíše v Dockeru. To by nám umožnilo snadno a “čistě” vytvářet a ukončovat instance backendu a nijak by to nenarušilo konzistenci testů. V rámci CICD by šlo tento přístup využít, ale Docker narušuje třetí požadavek zmíněný v úvodu, jelikož někteří z nás mají vývojové stroje na Windows, který komplikuje možnost integrovat Docker do testů. Nejedná se o nepřekonatelný problém, spíše otravný.

Třetí možností bylo vytvořit minimální verzi backendu, kterou by šlo snadno spouštět jak na vývojových strojích tak při CICD. Tato minimální verze by byla vytvořená pouze pro využití v testech a mohla by být napsaná např. v Pythonu, aby byl srozumitelný a rychle spustitelný. Jedinou nevýhodou je potřeba spouštět se Cypress testy a backend samostatně.

Naše řešení

Naše finální řešení vychází z uvědomění si, že minimální verzi backendu je možné napsat i přímo v JavaScriptu a že ho pak může spouštět Cypress v rámci konfigurace setupNodeEvents. Vytvořili jsme tedy backend na frontendu přes jeden soubor, který funguje jako klasický server, tedy poslouchá na vybraném portu a odpovídá na vybrané požadavky předdefinovanými odpověďmi. Díky tomu nám nevadí, že middleware není možné mockovat, protože celý server, na který se middleware dotazuje, je nastavený přesně pro potřeby testů. Výhodou tohoto přístupu je, že celý proces se spouští stejným přikazem jako samotné testy, takže ho lze pouštět jedním kliknutím na vývojovém stroji nebo jedním příkazem v rámci CICD.

frontend

NoxLabs je tým inženýrů a designérů, kteří se specializují na vývoj webových a mobilních aplikací. S nadšením vytváříme krásný software a vítáme nové nápady na projekty.

Chcete s námi spolupracovat? Napište nám