Testing React Apps (with Jest and React Testing Library)

Testing React Apps (with Jest and React Testing Library)

Geçen yazımda sizlere Jesti ve Javascript uygulamalarımızı nasıl test edeceğimi temel yapılarıyla anlatmıştım. Bu sefer ise sizlere React uygulamalarımızı Jesti ve react-testing-library’ı kullanarak React komponentlerimizi, adım adım nasıl test edeceğimizi göstereceğim.

React Testing Library

  • React Testing Library, React komponentlerimizi test edebilmemiz için geliştirilmiş en hafif, en güzel test yapısıdır React uygulamalarımız için.
  • Daha iyi test pratikleriyle şimdilerde Enzyme kullanımını bitirip yerini almaktadır.
  • Ve bizlere react-dom ve react-dom/test-utils ile çok güzel hazır utilty fonsiyonları sağlamaktadır.
  • create-react-app ile otomatik olarak hali hazırda react-testing-library birlikte gelmektedir.
  • Kent C. Dodds tarafından geliştirildi. Kesinlikle bloglarını takip edin ve makalelerini okuyun.

Kurulum

  • create-react-app ile uygulamanızı kurduysanız ekstra yapmanız veya yüklemeniz gereken hiçbir şey yok direkt test işlemlerine başlayabilirsiniz.
  • create-react-app olmadan bir boilerplate veya başka bir şekilde uygulamanızı oluşturduysanız tek yapmanız gereken aşağıda ki kod ile react-testing-library’ı uygulamamıza yüklemek.

npm install — save-dev [@testing](twitter.com/testing "Twitter profile for @testing")-library/react

START

  • Bir proje oluşturuyorum.

npx create-react-app component-testing

Counter.js

  • Daha sonra src altında “components” adında bir klasör oluşturuyorum. Ve aşağıdaki kodlarla Counter.js adında bir component dosyası oluşturuyorum.

  • Daha sonra npm test veya yarn test diyerek testi başlatacağız.

npm test / yarn test

Testing of <Counter /> Component

  • Counter.js dosyamızın yanında Counter.test.js veya Counter.spec.js adında bir dosya oluşturuyoruz. Bu dosya içinde component testlerini uygulayacağız.

render

  • render ile componentlerimizi sanki bir DOM üzerinde render eder gibi render çıktıları alabilir ve bu render çıktısına göre test işlemlerimizi yapabiliriz.
  • @testing-library/react”’den render methodunu projemize dahil ediyoruz.
  • wrapper adında bir değişken oluşturup render methodunu kullanarak ve içine argument olarak test dosyamıza import ile dahil ettiğimiz Counter componentini yukarıda ki gibi() veriyoruz.

cleanup

  • DOM’u temizlemek için kullanılır.
  • cleanup metodu, afterEach ile kullanıldığında bize her seferinde DOMu temizleyip getirmesini sağlar. Yani cleanup DOM’u temizler, afterEach her test işlemin de yapılmasını sağlar. AafterEach sadece bulunduğu dosyada ki her test işlemini kapsar. Böylece testlerimiz her seferinde temiz bir DOM ile başlar ve daha güvenli testler yapabiliriz.

NewMovie.js

NewMovie.test.js

debug

  • Ve daha sonra wrapper değişkenine render’ın döndüğü debug ile aşağıda ki gibi bir çıktıyı terminalimizde test çalıştığı zaman görebiliriz.

Catch Elements

  • Elementleri ise DOM’dan yakalar gibi aşağıda ki gibi yakalayabilir ve test edebiliriz ama bu yöntemi seçmeyin sadece render’ın DOM’a ne kadar benzediğini size göstermek için bunu gösteriyorum.

screen

  • screen ile aslında browserda aşağıda ki resimde ki gördüğünüz DOM’da ki element özelliklerine göre seçimlerinizi yapabilirsiniz. Test işlemlerinde elementleri seçmek screenin bize verdiği methodları kullanarak element seçimlerini yapınız.

getBy… vs queryBy…

  • Element seçim methodlarına geçmeden önce getBy… ve queryBy… methodları ile alakalı şunu belirtmek istiyorum.
  • Bu iki method aslında ne kadar benzer işleri yapıyor gözüksede aralarında büyük bir fark vardır. Bu fark getBy… methodları ile alacağımız yani seçeceğimiz eleman kesinlikle orada yani DOMda olmalıdır, olmazsa hata verir, queryBy… methodları ile ise olmazsa sıkıntı yok ve test hata vermez, olursa da seçer ve devam eder gibi düşünebilirsiniz. queryBy… ve getBy… ile olan tüm methodlarda her şeyde bu böyledir.

getByRole

  • getByRole metodu ile aslında yukarıda gördüğünüz DOM’daki element özelliklerine göre seçim yaparız.
  • getByRole ilk argument olarak elementin ne olduğunu alır, (a, button, aside vs…), ikinci argument olarak ise bir obje alır. Bu objede aşağıda verdiğimiz name’de ise text eşleşmesi içeren string alır. Yani Butonda “Bana tıkla” yazıyorsa DOM üzerinde, name olarak da “Bana tıkla” veya bir regex tanımlayarak name eşleşmesi ile element seçebilirsiniz.
  • Aşağıda ki örnekte içerisinde decrement yazan bir butonu seçiyoruz.

getByText

  • getByText ile text eşleşmesine göre element seçimi yapabiliriz.
  • İçerisine argument olarak aradığımız elementin içerisinde olan stringi veririz.
  • Aşadğıda ki örnekte “current count” olan bir elementi seçmek istemişiz.

getByTestId

  • Veya dilersek elemente verdiğimiz bir data-testid attributesine görede seçme işlemini yapabiliriz. Yukarıda ki Counter componentımız da göreceğiniz üzere counter-button adında bir data-testid tanımlamışız. Onu seçmek için tek yapmamız gereken getByTestId’ye, data-testid attributesine tanımladığımız değeri argument olarak vermektir. Tıpkı aşağıda ki örnekte ki gibi.

Destructring Wrapper

  • Veya dilersek bunların(getbyText, getByTestId, getByRole) hepsini aşağıda ki gibi wrapper’dan object destructring yöntemi ile de elde edebiliriz.

.textContent

  • Bu property ile de seçtiğimiz elementlerin içerdiği textleri alabiliriz. Daha sornada aşağıda ki gibi jest’in expect ve toBe’si ile bunları kontrol ve test edebiliriz.
  • Aşağıda data-testid attributesi counter-button olan elementi seçip daha sonrada textContent’ini almışız ve expect yapmışız. Ve toBe ile bu değerin 0 olmalı olduğunu belirtmişiz.

.toHaveTextContent

  • Tabi yukarıda ki gibi .textContent ve .toBe kullanacağınıza aşağıda ki gibi toHaveTextContent ile bu işlemi tek adım da yapabilirsiniz.

Which Query Should I Use? Hangi Query Seçicisini Kullanmalıyız?

  • Peki siz de yukarı daki soruyu soruyorsanız kesin bir cevabı yok bunu kullanacağınız yere göre kendinizin bulması gerekiyor. Ama screen ile yukarıda ki gibi seçme işlemlerinin herhangi birisini yapabilirsiniz. Ve tabi ki react-testing-library’ın documentationından yardım almayı kesinlikle unutmayın.

https://testing-library.com/docs/queries/about#priority

https://testing-library.com/docs/react-testing-library/cheatsheet/

  • Kent C. Dodds abimiz bu seçme işlemi için bize birde chrome extension geliştirmiş. İsterseniz bu eklentiye bir göz atın belki işinize yarar. Bu eklenti seçtiğiniz element için en iyi seçici hangisi size onu gösteriyor.

[Testing Playground
Simple and complete DOM testing playground that encourage good testing practices.testing-playground.com](https://testing-playground.com/ "testing-playground.com")

EVENTS

.fireEvent & .click

  • En önemli bir diğer konu ise events kısmı. Yani onClick, onChange gibi eventlerimizi yaptığımız kısımlar. Bunun için “fireEvent” methodunu @testing-library/react’den aşağıda ki gibi test dosyamıza dahil ediyoruz.
  • fireEvent ile değişik onlarca methodu kullanarak event işlemlerimizi gerçekleştiririz. Aşağıda bu eventlere örnek olsun diye ekran görüntüsü ekliyorum. Detaylı event kullanımı için Documentation’a bakmayı unutmayın.

userEvent - @testing-library/user-event & .click

  • userEvent ise @testing-library için tamamlayıcı bir kütüphanedir.
  • Bize fireEvent’e göre daha gelişmiş bir browser similasyonu sağlar.
  • Documentation’a bakmayı unutmayın.

  • Kullanım olarak aşağıda ki örnekte göreceğiniz üzere değişen çok fazla şey yok aslında kullanımı oldukça benzer.
  • Tabi ilk önce @testing-library/react’dan userEvent veya belirlediğiniz bir isim ile import etmeyi unutmuyoruz aşağıda ki gibi.

Login.js

  • Components kısmında aşağıda ki gibi Login.js component’i oluşturuyoruz.

Testing of Component

  • Bazende inputlarımızı test etmemiz gerekebilir bunun içinde .type methodu var bizim için fireEvent methodu veya userEvent library’si içinde.

.type
.toEqual

  • type eventi 2 argument alır. 1.si seçilen element, (type edileceği için input olmalı), 2. si ise type edilecek değer olmalı “mucahidyazar” veya const ile aşağıda ki gibi bir değişkene atanmış string olabilir.
  • Aşağıda login formumuzu render ettikten sonra, login formumuzda ki username ve password alanına girilecek değerleri degişkene tanımlayarak tutuyoruz.
  • Daha sonra screen ile username ve password input alanini label’in inputlarina göre seçiyoruz. Ve bunlara userEvent.type yapıp, aşağıda ki gibi oluşturduğumuz bu değişkenleri input’a type ettiriyoruz yani yazdırıyoruz.
  • Daha sonra getByRole ile butonun rolüne göre seçiyoruz ve userEvent.click ile formumuzu submit ediyoruz.
  • Submit olunca tetiklenecek handleSubmit fonksiyonunu yukarıda oluşturup Login componentına props olarak veriyoruz. Ve bu fonksiyonun gelen datayı submitedData ya eşitlemesini istiyoruz. Böylelikle submitedData’yı expect ile aliyoruz ve toEqual ile Login componentından gelen data objesinin username ve password gönderdiğine emin oluyoruz.

  • Veya yukarıda ki aynı işleri jest.fn() yani mock functionlarla aşağıda ki gibi çok daha basit ve test edilebilir bir şekilde de yapabiliriz.

  • Oluşturduğumuz mock functionunun username ve password ile kullanıldığını, toHaveBeenCalledWith ile anlayabiliyoruz.
  • Ve toHaveBeenCalledTimes ile submit fonksiyonunun bir kere çalıştırıldığından emin oluyoruz.

Create Fake Forms

  • Bazen de fake formlar oluşturmayı isteyebiliriz, o zaman yapacağımız 2 yöntem var.

  • Ya faker modulu ile aşağıda ki gibi fake username ve passwordları bir utility function ile oluşturacağız ve bu fonksiyonu kullanarak username ve password alacağız,

2. Yada @jackfranklin/test-data-bot modülünden gelen fake metodu ile oluşturacağız ve aynı faker modülü ile yaptığımız gibi çağırıp kullanacağız.

  • Ve aşağıda oluşturduğumu bu fake form functionları ile fake formlar oluşturup daha sonra object destructring ile field’ları yakalayabilir ve kullanabiliriz.

Mocking Http Request

  • Buda testlerde ki önemli konulardan birisi aslında. Mock olarak http Requestleri oluşturmak ve kullanmamız gereken zamanlar da olacak o zamanda sizde burada anlatacağım örnekte ki gibi kendi yapılarınızı kurmanız gerekecektir.

- İlk önce msv library’sinden rest’i import ediyoruz.
- Daha sonra msw/node’dan setupserver’ı import ediyoruz.
- setupServer ile fake sanal server kurup ve içinde de asağıda ki gibi rest ile fake request istekleri gönderdiğimiz url stringine göre return olarak fake jsonlar döneceğiz. Daha sonra aşağıda ki gibi bir değişkene atayarak kullanıyoruz. İlk argument’e işlemin yapılacağı url’i veriyoruz, ikinci argument’e ise async bir işlem yapan fonksiyonumuzu tanımlıyoruz. Username veya password yoksa status 400, ve error message döndürüyoruz. Eğer username ve password varsa da success ve return olarak da sadece username dönüyoruz.
- Ve daha sonra serverımızı beforeAll içinde tüm testlerden önce başlatıyoruz ve en sonunda afterAll içinde tüm testlerden sonra kapatılmasını sağlıyoruz ki server bellekte yer kaplamasın.
- Ve en son submit islemini yaptiktan sonrada DOM’dan, react-testing-library’den aldigimiz waitForElementToBeRemoved metodu ile loading yazan elementin yok olmasini beklemesini soyluyoruz.
- Ve loading yok olduktan sonra rest den donen username nin dom’da bir elemente yazildigini expect ile control ediyoruz.

  • Başka bir test oluşturup password alanına birşey girilmediğinde ki error’u yakalamak içinse tek yapmamız gereken aşağıda ki gibi password’a birşey type etmemek ve loading’den sonra gözükecek alert butonunu screenle yakalayıp expect ile içinde yazanı kontrol etmektir.

Inline Snapshots

.toMatchInlineSnapshot

  • Inline snapshotlarda ise aşagıda ki gibi ilk testlerde argument olarak bir şey vermeden toMatchInlineSnapshot methodunu çalıştırırız. Aşağıdaki gibi.

  • Daha sonra aşağıda ki gibi snapshotlar bu metodumuzun parantez içine gelirler.
    Daha sonra burada ki kod veya textContent değişirse snapshot hataları alırız ve bunları güncellemek update etmek istersek, terminalde u ya basarak güncelleriz veya dönen contenti veya kodu düzenleriz.

.afterEach
.resetHandlers()

  • Daha önce oluşturduğumuz fake servera bazen bazı durumlarda fake error mesajlar dönerek aşağıda ki gibi test etmek isteyebiliriz. Fakat bu istekler eğer bir şey yapmazsak kalıcı olurlar ve testi bozabilirler, bunun için afterEach ile her testten sonra serverın resetHandlers ile server requestlerini sıfırlıyoruz.

MOCK BROWSER APIs and MODULES

  • Bazen browser API’larına erişip test etmek isteyebiliriz. Örneğin window pencere boyutu gibi, veya GeoLocation API gibi.
  • Simdi aşağıda browserımızda ki geolocation API’ını nasıl test edeceğimizi göstereceğim.

Location.js

  • components klasörümüzün içine aşağıdaki Location.js dosyasındaki componentı oluşturuyoruz.
  • Bu componentimiz browserın geolocation API’ı kullanarak yerimizi bulmak istiyor izin verirsek Lokasyonumuz p elementleri içinde DOM’a yazılıyor. Eğer izin vermezsek error dönüyor ve error mesajı DOM’da dive yazıyor. Eğer sayfa açıldığın da izin isteme penceresine cevap vermezsek, error ve location’da dönmeyeceği için p elementi içinde Loading yazısı gözükecektir.

Testing of Component

  • BeforeAll ile her testten önce window.navigator.geolocation’daki getCurrentPosition’u method olarak jest function ile mock function olarak belirliyoruz.
  • Deferred’in görevi ise location direk olarak yazdırmak yerine location olmadan önce DOM nasıl olacak testi yapıyoruz. Daha sonrada callback’den dönen promise ile locationlari componentımıza ilettikten sonra ki testleri yapabilmemizi sağlıyor.
  • Promise ve resolve keylerini deferred’den alıp resolve yaparak mock functionu çalıştırır, awaitle promiseyi bekler ve daha sonrada Locationun render edilmiş halini alırız.

  • Yada aşağıda ki gibi ject mock function ile aynı işlemi ve testi yapabiliriz.

CUSTOM RENDER METHOD

  • Bundan önce tüm herşeyi render ederek testlerimizi yapıyorduk, aşağıda ise sadece istediğimiz bir şeyi render ederek sadece render ettiğimiz o component’ı test etmeyi göreceğiz.
  • Şimdi aşağıda burada kullanacağımız tüm Component’ları oluşturacağız. Bunlar ThemeProvider.js, EasyButton.js ve App.js

ThemeProvider.js - Görevi theme state’ini tutmak ve ana uygulamamızın etrafını sararak tema değiştiğindeki görsel değişikliği DOM’a uygulamak.

EasyButton.js Görevi ThemeProvide.js’den gelen useTheme custom hooks’u ile, temaya erişmek ve temaya göre kendisine style özellikleri belirlemek.

App.js Görevi uygulamada ki componentsların hepsi buradadır.
ThemeProvider burada etrafına sarılıdır ana uygulamamızın.
ThemeToggler uygulamamızın temasını değiştiren butona sahip componentdir.

  • İlk olarak normalde tüm her şeyi render ederek test yaptığımız şekli buraya bırakıyorum.

  • Burada ise sadece EasyButton’ı render ediyoruz. İlk önce Wrapperımızı yani EasyButton’un içinde olduğu Wrapperı bir degişkene atayarak oluşturuyoruz aşağıda ki gibi.

  • Daha sonra test edeceğimiz rendere vereceğimiz EasyButton gibi elemente, ikinci argument olarak olusturdugumuz Wrapper componentini wrapper keyi olarak aşağıda ki gibi atıyoruz.
  • Ve kalan test işlemi burada da aynı.

  • Veya istersek Wrapper yapımızı daha fonksiyonel bir yapıya çevirmek için function olarak tanımlarız ve aşağıda ki gibi çağırarak kullanabiliriz.

  • Veya render methoduna herhangi bir ayar eklemek istersek …options gibi bir yapı kurarak, renderWithTheme functionumuza göndereceğimiz argumentlerle ayarlarımızı ekleyebiliriz.

  • Ve return ederiz ki renderi asağıda ki yorum satırlarında ki gibi renderdan dönen diğer özellikleri kullanabilelim.

Testing Render Utils

  • Render için aşağıda ki gibi ayrı bir utils functionumuz olması her zaman için daha iyidir. Kendinize uygun bu şekilde bir render utilsi oluşturun.

  • Daha sonra aşağıda ki gibi kullanabiliriz.

  • Diyelim ki test klasöründe ki testutils’i aslında ../../ bu şekilde import etmemiz gerekiyor olabilir. Ve bunu yapmamak için aşağıdaki gibi ayarla src’yi baslangıç dizini olarak yapabilir ve noktalarla geriye çıkmamıza gerek kalmadan dosyalarımızı import edebiliriz. Bunun için jest.config.js dosyamızda aşağıda ki resimdeki gibi ayarlamayı yapmamız gerekmektedir. Detaylar için jest documentationa bakın.

Custom Hook Testing

  • Bazen de aşağıda ki custom hook gibi kendi oluşturduğumuz hooksları test etmek isteyebiliriz. Burada da onu göreceğiz.

useCounter.js

  • Aşağıda count ve onu arttırıp, azaltmamıza yarayacak functionalityi dönen bir custom hook oluşturduk.

  • Ve burada da şimdiye kadar öğrendiklerimiz ile test ediyoruz.

ACT

  • Bazen de test içinde component oluşturabiliriz hooksları denemek için. Component dışında bir degişken belirleyerek component içinde hooks, dışarıda belirlediğimiz o değişkene atarız ve daha sonra o değişkeni test edebiliriz.
  • Atadığımız değişkenin çıktısı aşağıda ki gibidir.
  • Bunları tetiklemek içinse react-testing-library’deki act’i kullanırız.

  • Veya aşağıda ki gibi bir ortak fonksiyon oluşturup, bu şekilde de kullanabiliriz. Ama resulta bu sefer current’dan ulaşıyoruz.

@testing-library/react-hooks

  • React hooksun ayrıca kendisine has hooks test libraryside bulunmaktadır.
  • Basit olarak ilk testte kullanımını görebilirsiniz.
  • Hooksa değer vermek içinse ikinci argument olarak hooksa göndereceğimiz degerleri verebiliriz. (Örnekteki 2. ve 3. testte olduğu gibi.)

  • Veya rerenderi kullanarak yeni değerler ekleyebilir ve sonrasın da yeni değerlere göre test edebiliriz aşağıda ki gibi.

.toMatchSnapshot - Snapshot Test

  • Daha önce inline snapshot testleri yapmıştık. Şimdi ise outline snapshot testi yapacağız.
  • Snapshot testlerini yapabilmek için screen ile snapshot test yapacağımız elementimizi ilk önce seçiyoruz, daha sonra expecte seçtiğimiz bu elementi veriyoruz, ve sonra da .toMatchSnapShot() ile snapshotumızı oluşturuyoruz.
  • Oluşturduğumuz snapshotlar yaptığınız zaman göreceğiniz üzere __snaapshot__ şeklinde farklı bir klasörde dosyaAdi.spec/test.js.snap şeklinde oluşacakdır. Aşağıda ki resimde ki gibi.

  • Daha önce Coounter.js diye bir componentımız vardı aşağıda ki gibi. Ben buna birde en dışına data-testid attribute’si olarak counter-container ekledim. Ve ben bunu seçerek Counter componentinin snapshotunu alıyorum.

Counter.js

  • Aşağıya yukarıda ki componentin test ve snapshot dosyalarını bırakıyorum.

Counter.spec.js

Counter.spec.js.snap

  • Snap dosyasını oluşturduktan sonra snap dosyasında ki html veya test edilen component daki html değişirse eğer bir sonraki testte snapshot fail olur.
  • Snapshotu güncelleyerek yukarıda ki hatayı giderebilir ve yeni snapshot çıktısı alabiliriz, güncellemek için test konsolu açıkken u ya basınız. Hatayı gidermek için bir diğer yol ise yapılan değişiklikleri geri almak veya düzeltmektir.

waitForElement(fn) - Asenkron işlemler

  • Jest ve react-testing-library ile ile testlerimizde async işlemlerimizi waitForElement metoduyla gerçekleştiririz.
  • waitForElement içine bir fonksiyon alır ve bu fonksiyon içinde beklemesini istediğimiz elementleri yazarız. Aşağıda ki mod alandaki gibi.
  • waitForElement getByTestId ile movie-title’ı arar ve bulduktan sonra aşağıda ki expecte geçer ve onu yapmaya çalışır.
  • Ve önemli olan bir diğer nokta ise async işlem yapacaksak testin functionunu async olarak başlatmalıyız aşagidaki gibi.

  • Aşağıda da MovieList.js component testini göreceğiz.