Heroes bileşenine geri döndük ve bu sefer yönlendirme özelliklerine sahibiz. İlk render'da Heroes, alt HeroList bileşenini gösterir. ListHeaderdaki + düğmesine tıklayarak HeroDetaili görüntüleyebilmemiz gerekiyor. Daha sonra ListHeaderdaki yenileme düğmesine tıkladığında HeroListi tekrar görüntülememiz gerekiyor. Cancel düğmesi HeroDetailden HeroListe geri dönmelidir. Yeni bir dal oluşturun feat/Heroes-part2
Şimdilik, rotaya bağlı olarak HeroList ve HeroDetail arasında geçiş yapmak yerine, her ikisini bir arada gösterelim. Testi yazalım (Red 1).
// src/heroes/Heroes.cy.tsximport Heroes from"./Heroes";import { BrowserRouter } from"react-router-dom";import"../styles.scss";describe("Heroes", () => {it("should handle hero add and refresh", () => {cy.window().its("console").then((console) =>cy.spy(console,"log").as("log"));cy.mount( <BrowserRouter> <Heroes /> </BrowserRouter> );cy.getByCy("list-header");cy.getByCy("add-button").click();cy.get("@log").should("have.been.calledWith","handleAdd");cy.getByCy("refresh-button").click();cy.get("@log").should("have.been.calledWith","handleRefresh"); });it.only("should display hero list on render", () => {cy.mount( <BrowserRouter> <Heroes /> </BrowserRouter> );cy.getByCy("hero-list");cy.getByCy("hero-detail"); });constinvokeHeroDelete= () => {cy.getByCy("delete-button").first().click();cy.getByCy("modal-yes-no").should("be.visible"); };it("should go through the modal flow", () => {cy.window().its("console").then((console) =>cy.spy(console,"log").as("log"));cy.mount( <BrowserRouter> <Heroes /> </BrowserRouter> );cy.getByCy("modal-yes-no").should("not.exist");cy.log("do not delete flow");invokeHeroDelete();cy.getByCy("button-no").click();cy.getByCy("modal-yes-no").should("not.exist");cy.log("delete flow");invokeHeroDelete();cy.getByCy("button-yes").click();cy.getByCy("modal-yes-no").should("not.exist");cy.get("@log").should("have.been.calledWith","handleDeleteFromModal"); });});
Testi geçmek için, HeroListin yanına HeroDetaili ekleyin. Geçici olarak heroes dizisinin 0. indeksi olan bir hero özelliği olabilir (Green 1).
Yolu değiştirdiğinde görüntülenen bileşeni değiştirmek istiyoruz. React'te bunu yönlendiren şey, önce yol, ardından alt bileşendir, react-router bölümünde gördüğümüz gibi. Bir bileşen testinde başlangıçta url yoktur, ancak bir bağlantıya tıkladığında yol değişir. Yolu kontrol etmek için yenileme ve + düğmeleri ile testi kullanabiliriz. Bu testte yolu kontrol etmek için cy.location kullanacağız. İşte Gleb Bahmutov'un Cypress ipuçlarından bir alıntı:
cy.visit("https://example.cypress.io/commands/location?search=value#top");// yields a specific part of the locationcy.location("protocol").should("equal","https:");cy.location("hostname").should("equal","example.cypress.io");cy.location("pathname").should("equal","/commands/location");cy.location("search").should("equal","?search=value");cy.location("hash").should("equal","#top");
Kısalık adına, test kodunu .only bölümüne odaklı tutacağız. Yenileme düğmesine tıklayarak yolun /heroes olmasını kontrol eden bir test yazıyoruz (Red 2).
// src/heroes/Heroes.cy.tsxit.only("should display hero list on render", () => {cy.mount( <BrowserRouter> <Heroes /> </BrowserRouter> );cy.getByCy("hero-list");cy.getByCy("hero-detail");cy.getByCy("refresh-button").click();cy.location("pathname").should("eq","/heroes");});
Test başarısız oldu, ancak konsolda handleRefresh günlüğünü görüyoruz. Günlüğün yerine, url'yi değiştiren bir şey kullanabiliriz. React-router'ın useNavigate işlevi, programlamalı olarak herhangi bir url'ye yönlendirmemize olanak tanır (Green 2).
Yol adlarımız iyi görünüyor, ancak ihtiyacımız olan şey, rotaya bağlı olarak farklı bileşenlerin görüntülenmesi.
react-router soyut rotalar
İlk işlemde HeroList'i görmek istiyoruz.
ListHeader'daki ekle düğmesine tıkladığında, HeroDetail'i görmek istiyoruz.
ListHeader'daki yenile düğmesine tıkladığında tekrar HeroList'i görmek istiyoruz, böyle devam eder.
Burada biraz React Router v6 bilgisine ihtiyacımız var. Üst düzey uygulama bileşenindeki react-router kurulumunu hatırlayın. Burada /heroes rotası ile ilgileniyoruz. Yol adı sadece /heroes olduğunda HeroesList'i görüntülemek istiyoruz, /heroes/add-hero olduğunda HeroDetail'i görüntülemek istiyoruz. Bu, /heroes için soyut bir rota gerektireceği anlamına gelir.
react-router v6'da, bir rotanın soyut ağacında başka bir <Routes> olduğunda sonunda *'a ihtiyacımız var. Bu durumda, soyut <Routes> kalan yol adı kısmını eşleştirir. App.tsx dosyasındaki path="/heroes" özelliğini path="heroes/*" olarak değiştirmemiz gerekiyor. Bu, ekleyeceğimiz soyut Routes bileşeninin rota kontrolünü devralmasına izin verecektir.
Bu yapıyla 2 başarısızlık var; test hiçbir şey görüntülemediği için başarısız oluyor, TS HeroDetail'in tanımlı bir hero ile hero özelliğine sahip olmasını istediği için hata veriyor (Kırmızı 4).
HeroDetail'i iki koşulda kullanmak için ayarlamıştık; heroId alanını heroId varsa veya yoksa görüntülemek için kullanabiliriz. Bu nedenle, yeni bir kahraman eklemek için bileşeni kullanabilmeliyiz. Şimdilik özelliği isteğe bağlı yapabiliriz ve boş id, name ve description özelliklerine sahip varsayılan bir kahraman nesnesi oluşturabiliriz. Şimdilik HeroDetail şu şekilde:
HeroDetail.cy.tsx'in o değişiklikten sonra geçiyor olması harika. Tek endişemiz, Heroes testimizi bozmuş olmamız.
// src/heroes/Heroes.cy.tsxit.only("should display hero list on render", () => {cy.mount( <BrowserRouter> <Heroes /> </BrowserRouter> );cy.getByCy("hero-list").should("be.visible");cy.getByCy("add-button").click();cy.location("pathname").should("eq","/heroes/add-hero");cy.getByCy("refresh-button").click();cy.location("pathname").should("eq","/heroes");});
react-router bölümünden hatırlayacağımız gibi, bir bileşen testi yollar hakkında fikir sahibi değildir ve teste tıklayarak gezinmediğimiz sürece, yol belirsizdir. Bu, geçersiz bir kahramanlar rotası için de bir test gerektirebilir; örneğin heroes/foo42. Böyle bir durum söz konusu olduğunda, var olmayan bir heroId arıyoruz, HeroListi görmek istiyoruz. Yolun * olduğu HeroListi oluşturan yeni bir Route öğesi eklememiz gerekiyor.
Bileşenin testte bağlantısı belirsiz olduğundan, url doğrulamayı da HeroListin oluşturulduğunu kontrol etmeye değiştirmeliyiz (Green 4).
// src/heroes/Heroes.cy.tsxit.only("should display the hero list on render", () => {cy.mount( <BrowserRouter> <Heroes /> </BrowserRouter> );cy.getByCy("hero-list").should("be.visible");cy.getByCy("add-button").click();cy.location("pathname").should("eq","/heroes/add-hero");cy.getByCy("refresh-button").click();cy.location("pathname").should("eq","/heroes");});
Bu değişiklik, testin çalışmasını sağlar, ancak süite ilk iki it bloğu arasında uyumlu bir anlam kazandırmaz. handleAdd ve handleRefresh üzerindeki console.log'ları kontrol etmek için yapılan ilk test artık geçerli değil ve artık gerekmeyecek çünkü rotayı useNavigate ile değiştiriyoruz. useNavigate üzerinde casusluk yapabiliriz, ancak bu uygulama detayı ve zaten url'nin değiştiğini kontrol ediyoruz; daha yüksek seviyede, ekstra maliyet olmadan şeyleri daha iyi bir şekilde test ediyoruz. İşte testin yeniden düzenlenmesi (Düzenleme 4):
// src/heroes/Heroes.cy.tsximport Heroes from"./Heroes";import { BrowserRouter } from"react-router-dom";import"../styles.scss";describe("Heroes", () => {it("should display the hero list on render, and go through hero add & refresh flow", () => {cy.mount( <BrowserRouter> <Heroes /> </BrowserRouter> );cy.getByCy("list-header").should("be.visible");cy.getByCy("hero-list").should("be.visible");cy.getByCy("add-button").click();cy.location("pathname").should("eq","/heroes/add-hero");cy.getByCy("refresh-button").click();cy.location("pathname").should("eq","/heroes"); });constinvokeHeroDelete= () => {cy.getByCy("delete-button").first().click();cy.getByCy("modal-yes-no").should("be.visible"); };it("should go through the modal flow", () => {cy.window().its("console").then((console) =>cy.spy(console,"log").as("log"));cy.mount( <BrowserRouter> <Heroes /> </BrowserRouter> );cy.getByCy("modal-yes-no").should("not.exist");cy.log("do not delete flow");invokeHeroDelete();cy.getByCy("button-no").click();cy.getByCy("modal-yes-no").should("not.exist");cy.log("delete flow");invokeHeroDelete();cy.getByCy("button-yes").click();cy.getByCy("modal-yes-no").should("not.exist");cy.get("@log").should("have.been.calledWith","handleDeleteFromModal"); });});
react-router bölümünde, yönlendirmeyi e2e testleri ile test etmenin en iyi yoluna karar verdik. Bileşenlerde yolları test ediyoruz, ancak yol değiştiğinde doğru alt bileşenin oluşturulup oluşturulmadığını test edemiyoruz. Benzer bir akışı kapsayan e2e testi başlatabiliriz, bu da gelecekte CRUD kahraman akışını kapsayan daha büyük bir test olarak hizmet edecektir. Daha düşük seviyede test edemeyeceğimiz veya güvenle test edemeyeceğimiz bir işlevsellik olduğunda, test piramidinde yukarı çıkarız, bu durumda bileşen testinden e2e testine. yarn cy:open-e2e ile e2e koşucusunu başlatın. cypress/e2e/create-hero.cy.ts adlı yeni bir e2e test oluşturun.
// cypress/e2e/create-hero.cy.tsdescribe("Create hero", () => {beforeEach(() =>cy.visit("/"));it("should go through the refresh flow", () => {cy.location("pathname").should("eq","/heroes");cy.getByCy("add-button").click();cy.location("pathname").should("eq","/heroes/add-hero");cy.getByCy("hero-detail").should("be.visible");cy.getByCy("input-detail-id").should("not.exist");cy.getByCy("refresh-button").click();cy.location("pathname").should("eq","/heroes");cy.getByCy("hero-list").should("be.visible"); });});
Bu test, HeroDetail bileşenin ekleme sırasında nasıl görüntülendiğini ve yeni bir kahraman olduğundan id alanı olmadan nasıl görüntüleneceğini kontrol etmemize olanak tanır. Yenileme kahramanı oluşturma akışı bize yeni bir fikir verir; arka uç çalışıyor olsun veya olmasın, kahraman düzenleme veya ekleme için iptal akışı da çalışmalıdır. Belirli bir dönüm noktasına ulaştıktan sonra, uçtan uca testler veya uygulamanın basitçe reklam amaçlı kullanımı genellikle bize yeni özellikler için fikirler sunar. Sonuçta bu, bilimsel yöntemdir, şimdi daha fazlasını biliyoruz ve daha fazlasını denemek için çalışabiliriz ve bu, TDD'nin arkasındaki orijinal düşünceyi ve çevikliği esas alır.
Düzenleme kahramanı iptal akışı için başarısız bir e2e testi ekleyelim (Kırmızı 5). cypress/e2e/edit-hero.cy.ts adında bir dosya oluşturun. Ekleme akışına benzer şekilde başlar, ancak Düzenle düğmesine tıklar ve ilgili bir rota içinde olmayı bekler.
// cypress/e2e/edit-hero.cy.tsdescribe("Edit hero", () => {beforeEach(() =>cy.visit("/"));it("should go through the cancel flow", () => {cy.location("pathname").should("eq","/heroes");cy.getByCy("edit-button").first().click();cy.location("pathname").should("eq","/heroes/edit-hero/HeroAslaug"); });});
Uygulamamızdaki tıklama işleyicileriyle ne yapacağımızdan emin olmadığımızda, onları console.log ile başlattık. E2e testinin konsolunda handleSelectHero'yu görebiliriz. Bu işlev HeroList bileşeninde bulunmaktadır. Sadece onu, ana Heroes bileşeninde yaptığımız gibi useNavigate kullanacak şekilde geliştirmemiz gerekiyor (Yeşil 5).
İlk kahramana gidebiliriz, ancak başka bir kahramana gidip doğru URL'de bitebilir miyiz? Bunu test etmek için bir test yazalım (Kırmızı 6).
// cypress/e2e/edit-hero.cy.tsdescribe("Edit hero", () => {beforeEach(() =>cy.visit("/"));it("should go through the cancel flow", () => {cy.location("pathname").should("eq","/heroes");cy.getByCy("edit-button").first().click();cy.location("pathname").should("eq","/heroes/edit-hero/HeroAslaug"); });it("should go through the cancel flow for another hero", () => {cy.location("pathname").should("eq","/heroes");cy.getByCy("edit-button").eq(1).click();cy.location("pathname").should("eq","/heroes/edit-hero/HeroBjorn"); });});
Test başarısız oluyor çünkü react-routerın rota parametresini bilmeye ihtiyacı var. heroId navigasyonundan daha iyi bir şey yapabilmemiz gerekiyor. Kahramanı düzenlerken, bu heroId'yi bileşene iletilen heroes özelliğinden alabilmeliyiz. handleSelectHero, id'yi bir argüman olarak almalı ve ona gitmelidir.
Bu değişiklik, şimdi handleSelectHero'nun bir argüman beklemesi nedeniyle ButtonFooer'da bir tür hatasına neden oluyor. Bileşeni şu şekilde güncelleyebiliriz (Yeşil 6):
Testi geliştirelim ve bir kahramanı düzenlerken sadece doğru URL yoluna sahip olmakla kalmayıp, aynı zamanda HeroDetail'i gösterdiğimizi kontrol edelim (Kırmızı 7).
// cypress/e2e/edit-hero.cy.tsdescribe("Edit hero", () => {beforeEach(() =>cy.visit("/"));it("should go through the cancel flow", () => {cy.location("pathname").should("eq","/heroes");cy.getByCy("edit-button").first().click();cy.location("pathname").should("eq","/heroes/edit-hero/HeroAslaug");cy.location("pathname").should("include","/heroes/edit-hero/");cy.getByCy("hero-detail").should("be.visible"); });it("should go through the cancel flow for another hero", () => {cy.location("pathname").should("eq","/heroes");cy.getByCy("edit-button").eq(1).click();cy.location("pathname").should("eq","/heroes/edit-hero/HeroBjorn");cy.location("pathname").should("include","/heroes/edit-hero/");cy.getByCy("hero-detail").should("be.visible"); });});
Yoldaki heroId'yi çıkarmanın ve bileşenin bunu bilmesini sağlamanın bir yoluna ihtiyacımız var. react-routerda yol özelliklerinden ve useParam kancasından yararlanabiliriz. İşte yol özelliklerinin nasıl çalıştığını gösteren basit bir örnek. Verimizin milkshake olduğunu ve veri modelinin şu şekilde göründüğünü varsayalım:
Bu yapılandırmayı yeniden oluşturmak için, edit-hero yolunun id adında bir yol özelliğine ihtiyacı vardır ve URL'den bu yol özelliğini çıkarmanın bir yoluna ihtiyacımız vardır. React-router'ın useParamı, URL parametrelerine karşılık gelen özelliklere sahip bir nesne döndürür..
const { flavor,size } =useParams();
Bu bilgiyi uygulamamıza aktararak, veri şu şekildedir:
Test geçiyor, heroId ile doğru URL'ye sahibiz, HeroDetails görüntüleniyor, ancak heroId alanı görüntülenmiyor.
Bir kahramanı düzenlerken heroId alanının görünür olduğundan emin olmak için bir test daha yazıyoruz (Kırmızı 8).
// cypress/e2e/edit-hero.cy.tsdescribe("Edit hero", () => {beforeEach(() =>cy.visit("/"));it("should go through the cancel flow", () => {cy.location("pathname").should("eq","/heroes");cy.getByCy("edit-button").first().click();cy.location("pathname").should("eq","/heroes/edit-hero/HeroAslaug");cy.location("pathname").should("include","/heroes/edit-hero/");cy.getByCy("hero-detail").should("be.visible");cy.getByCy("input-detail-id").should("be.visible"); });it("should go through the cancel flow for another hero", () => {cy.location("pathname").should("eq","/heroes");cy.getByCy("edit-button").eq(1).click();cy.location("pathname").should("eq","/heroes/edit-hero/HeroBjorn");cy.location("pathname").should("include","/heroes/edit-hero/");cy.getByCy("hero-detail").should("be.visible");cy.getByCy("input-detail-id").should("be.visible"); });});
URL'den id değerini almak için, useParams() ile const { id } = useParams() kullanarak rotayı bileşene bağlayın. Kahraman verilerine güvenmek yerine, URL'den aldığımız yol özniteliğine güvenmek istiyoruz ve useParams bunun için kullanılacak kancadır. Ayrıca, doğrudan bir URL'ye yönlendirebilme yan faydasına da sahibiz (Yeşil 8).
URL'deki kahramanın id değerinden bağımsız olarak, id alanı yol özniteliği değeri ile görüntülenir.
Eğer URL'den kahramanın id değerini alabiliyorsak, neden name ve description değerlerini de almayalım ki? Ad ve açıklama alanlarının da doldurulduğunu kontrol etmek için testleri geliştirelim (Kırmızı 9). fixtures/heroes.json dosyasını kullanarak verileri sahteleyebiliriz. Ad ve açıklama için verilerin alanlarda görüntülendiğini doğrulamak istiyoruz (Kırmızı 9).
// cypress/e2e/edit-hero.cy.tsdescribe("Edit hero", () => {beforeEach(() =>cy.visit("/"));it("should go through the cancel flow", () => {cy.location("pathname").should("eq","/heroes");cy.fixture("heroes").then((heroes) => {cy.getByCy("edit-button").eq(0).click();cy.location("pathname").should("include",`/heroes/edit-hero/${heroes[0].id}` );cy.getByCy("hero-detail").should("be.visible");cy.getByCy("input-detail-id").should("be.visible");cy.findByDisplayValue(heroes[0].id);cy.findByDisplayValue(heroes[0].name);cy.findByDisplayValue(heroes[0].description); }); });it("should go through the cancel flow for another hero", () => {cy.location("pathname").should("eq","/heroes");cy.fixture("heroes").then((heroes) => {cy.getByCy("edit-button").eq(1).click();cy.location("pathname").should("include",`/heroes/edit-hero/${heroes[1].id}` );cy.getByCy("hero-detail").should("be.visible");cy.getByCy("input-detail-id").should("be.visible");cy.findByDisplayValue(heroes[1].id);cy.findByDisplayValue(heroes[1].name);cy.findByDisplayValue(heroes[1].description); }); });});
HeroList bileşeni tüm kahramanlar hakkında bilgi sahibidir ve Edit düğmesine tıklayarak ilgili kahramana götürebiliriz. Daha önce bunu şöyle ifade ettik:
Heroes rotası yolunu şu şekilde değiştirerek daha fazla rota parametresi ekleyebiliriz: <Route *path*="/edit-hero/:id/:name/:description" *element*={<HeroDetail />} />.
Ancak, react-router ayarlarını değiştirmeden arama parametrelerini kullanmak daha iyidir. İşte handleSelectHero arama parametreleri ile nasıl görünür:
Verilen heroes dizisi, bileşene bir prop olarak geçirilirken, heroId'den hero.name ve hero.description'ı çıkarmamız için bir yol bulmamız gerekiyor. Array.find yöntemi, ihtiyacımız olan kahramanı bize sağlayabilir:
Test hala başarısız olsa da, şimdi HeroList üzerinde Edite tıkladığınızda, HeroDetail URL'de ilgili tüm verilere sahip.
Şimdi, HeroDetail'e bakarken URL'den arama parametrelerini çıkarmak için bir yola ihtiyacımız var, böylece URL'den tüm hero durumunu (id, isim, açıklama) alabiliriz.
HeroDetails'ta İptal düğmesine bastığımızda HeroList'in görüntülenmesi gerekir. İşte başarısız olan testimiz (Kırmızı 10).
// cypress/e2e/edit-hero.cy.tsdescribe("Edit hero", () => {beforeEach(() =>cy.visit("/"));it("should go through the cancel flow", () => {cy.location("pathname").should("eq","/heroes");cy.fixture("heroes").then((heroes) => {cy.getByCy("edit-button").eq(0).click();cy.location("pathname").should("include",`/heroes/edit-hero/${heroes[0].id}` );cy.getByCy("hero-detail").should("be.visible");cy.getByCy("input-detail-id").should("be.visible");cy.findByDisplayValue(heroes[0].id);cy.findByDisplayValue(heroes[0].name);cy.findByDisplayValue(heroes[0].description);cy.getByCy("cancel-button").click();cy.location("pathname").should("eq","/heroes");cy.getByCy("hero-list").should("be.visible"); }); });it("should go through the cancel flow for another hero", () => {cy.location("pathname").should("eq","/heroes");cy.fixture("heroes").then((heroes) => {cy.getByCy("edit-button").eq(1).click();cy.location("pathname").should("include",`/heroes/edit-hero/${heroes[1].id}` );cy.getByCy("hero-detail").should("be.visible");cy.getByCy("input-detail-id").should("be.visible");cy.findByDisplayValue(heroes[1].id);cy.findByDisplayValue(heroes[1].name);cy.findByDisplayValue(heroes[1].description);cy.getByCy("cancel-button").click();cy.location("pathname").should("eq","/heroes");cy.getByCy("hero-list").should("be.visible"); }); });});
Eğer konsola bakarsak, handleCancel'in çağrıldığını görürüz. Bu fonksiyon da HeroDetail bileşeninde bulunuyor. İptal düğmesine tıkladığında URL'yi /heroes'e değiştirmek için bir kez daha useNavigate'i kullanabiliriz (Yeşil 10).
edit-hero e2e testindeki iptal akışı, add-hero akışına da uygulanır. İkinci testi add-heroya ekleyebiliriz, böylece yenileme akışındaki kontrolleri tekrarlamadan ve tıklama navigasyonu yerine doğrudan navigasyon kullanarak (Düzenleme 10).
// cypress/e2e/create-hero.cy.tsdescribe("Create hero", () => {it("should go through the refresh flow", () => {cy.visit("/");cy.location("pathname").should("eq","/heroes");cy.getByCy("add-button").click();cy.location("pathname").should("eq","/heroes/add-hero");cy.getByCy("hero-detail").should("be.visible");cy.getByCy("input-detail-id").should("not.exist");cy.getByCy("refresh-button").click();cy.location("pathname").should("eq","/heroes");cy.getByCy("hero-list").should("be.visible"); });it("should go through the cancel flow and perform direct navigation", () => {cy.visit("/heroes/add-hero");cy.getByCy("cancel-button").click();cy.location("pathname").should("eq","/heroes");cy.getByCy("hero-list").should("be.visible"); });});
Son yeniden düzenlemeler
HeroDetail bileşenine baktığımızda, useParams ve useSearchParams kullanarak URL'den tüm verileri alıyoruz. Artık hiçbir verinin özellik olarak geçmesine gerek kalmadı, çünkü id, name ve description'a sahibiz. Bunlar useState içinde başlatılabilir. İşte yeniden düzenleme:
Bir tür hatası alıyoruz çünkü HeroDetail bileşeni testleri hala hero özelliğini iletiyor. HeroDetail.cy.tsx ve HeroList.cy.tsx bileşen testlerine bakarak, useNavigate'in <Router> bileşeni altında kullanılması gerektiği konusunda bir hata alıyoruz. src/heroes/HeroList.cy.tsx dosyasında, mountları BrowserRouter içine alıyoruz. console.log'u kaldırıyoruz ve şimdi edit-button'a tıklandığında url'yi doğrulayabiliyoruz, bu da /heroes/edit-hero/<someHeroId> olmalı.
Ayrıca bir anti-kalıp fark ediyoruz; useState üzerinde casusluk yapıyoruz, bu da kahraman adı ve açıklamasını alanlara yazarken oluyor. Bu bir uygulama ayrıntısıdır ve eğer durum yönetimini değiştirirsek, testin bakımı gerekebilir. Daha çok siyah kutu yaklaşımına yönelebilir ve bileşenin nasıl çalışması gerektiğine dair daha iyi bir güven sağlayabiliriz. İşte güncellenmiş test:
Bu, currying için güzel bir kullanım örneğidir. Dış fonksiyon, özel argümanı alabilir ve olayı kabul eden bir işlev döndürebilir. HeroList'i şöyle yeniden düzenleyebiliriz:
Yeni bir dosya oluşturun, src/hooks/useHeroParams.ts ve kodu kancaya taşıyın. Tek fark, bu kanca içinde ihtiyacımız olan şeyi döndüren bir nesneyle dönüyoruz.
HeroDetaili HeroList ile birlikte işlemek için başarısız bir test ekledik (Kırmızı 1) ve iki bileşeni Heroesa ekledik (Yeşil 1).
ListHeaderdaki yenile düğmesine tıklarken url'yi inceleyen bir test yazdık (Kırmızı 2).
useNavigate kullanarak tıklama sonrası ihtiyaç duyduğumuz url'ye programlı olarak yönlendirdik (Yeşil 2).
Ekle düğmesi için benzer bir test ekledik, url'yi inceledik ve yine useNavigate kullandık (Kırmızı 3, Yeşil 3).
react-routerı alt rotalar için yapılandırdık, TS ve testi her şeyin çalıştığından emin olmak için güncelledik (Kırmızı 4, Yeşil 4). Testi daha uyumlu ve anlamlı olacak şekilde yeniden düzenledik (Düzenleme 4).
react-router bölümünü hatırlayarak, yönlendirmenin en iyi şekilde e2e ile test edildiğini, bileşenlerimizde güveni artırmak için e2e test ekledik; pathname'den daha fazlasını doğrulayın, alt bileşenlerin belirli bir şekilde işlemesini sağlayın. Kahraman oluşturma iptal akışı için e2e testimiz mükemmel çalıştı.
Sonra, düzenleme kahramanı iptal akışındaki benzerliği gördük ve bunun için başarısız bir e2e test ekledik. Düzenle düğmesine tıkladığında, /heroes/edit-hero/HeroAslaug gibi bir rota üzerinde olmak istedik (Kırmızı 5).
Zaten mevcut HeroList bileşeninin handleSelectHero işlevinde useNavigate kullanarak, aynı url'yi elle kodladık (Yeşil 5).
Herhangi bir kahramanı düzenleyip doğru rotaya ulaşıp ulaşamayacağımızı kontrol etmek için bir test ekledik (Kırmızı 6).
HeroList'teki sabit kodlu /HeroAslaug'u navigate'den kaldırdık, bunun yerine handleSelectHero'yu heroId argümanı tarafından yönlendirilen bir işlev haline getirdik (Yeşil 6).
Testi, doğru rotada olmanın yanı sıra HeroDetail'in gösterilip gösterilmediğini kontrol etmek için geliştirdik (Kırmızı 7)
Heroes düzenleme kahramanı rotasında :id rotası parametresini kullandık. :id, handleSelectHero'nun heroId argümanıyla eşleştiğinde, başarılı bir test elde ettik (Yeşil 7).
Testi, id alanının koşullu işlemesi için geliştirdik; eğer bir heroId varsa, bir alan olmalıdır (Kırmızı 8).
HeroDetail'de useParams kullanarak ve const {id} = useParams() ile :id yönlendirici parametresini eşleştirerek kullandık. Koşullu işlemede hero.id yerine id kullandık (Yeşil 8).
Bir kahramanı düzenlerken HeroDetail'de ad ve açıklamanın görüntülenip görüntülenmediğini kontrol ederek testi geliştirdik (Kırmızı 9).
Ek rotalar eklemek yerine HeroListhandleSelectHero'da arama parametrelerini kullanmaya karar verdik. useSearchParams, url'deki arama parametrelerini yakalamamıza yardımcı oldu (Yeşil 9)
Testi, iptal düğmesinin bizi HeroDetails'den HeroList bileşenine götürdüğünü kontrol etmek için geliştirdik (Kırmızı 10).
Ayrıca, Cancel üzerindeki HeroDetails'den HeroList'e yönlendirme aynı olduğundan, kahraman ekleme akışına da bir test ekledik (Düzenleme 10).
Çıkarımlar
Uygulama detaylarından daha yüksek seviyede test etmek, ekstra maliyetler olmaksızın idealdir. Örneğin, kancaların sonuçlarını test edebiliriz, kancanın çağrılıp çağrılmadığını kontrol ederiz; useNavigate ve url kontrolü yerine useNavigate üzerinde casusluk yapma.
Test etmekte zorlandığımız veya daha düşük seviyede güvenle test edemediğimiz işlevler varsa, test piramidinde yukarı çıkarız.
Belirli bir dönüm noktasına ulaştıktan sonra, e2e testler veya basitçe uygulamanın geçici kullanımı, genellikle bize daha yüksek düzeyde bir perspektif ve yeni özellikler için fikirler sunar. Sonuçta bu, bilimsel yöntemdir; artık daha fazlasını biliyoruz ve daha fazlasını denemeye çalışabiliriz ve bu, TDD'nin arkasındaki orijinal zihniyeti ve çevikliği esas alır.
TDD'de, testlerin geçmesini sağlamak için sabit kodlu değerler kullanmayı teşvik edilir.
Bileşen testlerinde, uygulama detaylarını test etmekten kaçınmak ve bileşenin nasıl çalışması gerektiği konusunda daha iyi güven sağlamak için daha çok siyah kutuya eğilebiliriz.
Olay işleyicilerini yeniden düzenlemek için currying kullanabiliriz: onClick={() => handleSelectHero(hero.id)} vs onClick={handleSelectHero(hero.id)}.
Bileşenlerdeki bazı mantığı soyutlamak için özel kanca kullanabiliriz.