Test Odaklı React.js Geliştirme: Enzyme ve Jest ile React.js Birim Testi
Yayınlanan: 2022-03-11Michael Feathers'a göre, testi olmayan herhangi bir kod parçasının eski kod olduğu söyleniyor. Bu nedenle, eski kod oluşturmaktan kaçınmanın en iyi yollarından biri test odaklı geliştirme (TDD) kullanmaktır.
JavaScript ve React.js birim testi için birçok araç mevcut olsa da, bu yazıda TDD kullanarak temel işlevselliğe sahip bir React.js bileşeni oluşturmak için Jest ve Enzyme kullanacağız.
Bir React.js Bileşeni Oluşturmak için Neden TDD Kullanılmalı?
TDD, kodunuza birçok fayda sağlar; yüksek test kapsamının avantajlarından biri, kodunuzu temiz ve işlevsel tutarken kolay kod yeniden düzenlemeye olanak sağlamasıdır.
Daha önce bir React.js bileşeni oluşturduysanız, kodun gerçekten hızlı büyüyebileceğini fark etmişsinizdir. Durum değişiklikleri ve servis çağrılarıyla ilgili ifadelerin neden olduğu birçok karmaşık koşulla doldurulur.
Birim testleri olmayan her bileşen, bakımı zor hale gelen eski koda sahiptir. Üretim kodunu oluşturduktan sonra birim testleri ekleyebiliriz. Ancak, test edilmesi gereken bazı senaryoları gözden kaçırma riskiyle karşı karşıya kalabiliriz. Önce testler oluşturarak, bileşenimizdeki her mantık senaryosunu kapsama şansımız daha yüksek, bu da yeniden düzenlemeyi ve bakımı kolaylaştıracak.
Bir React.js Bileşenini Nasıl Birim Test Ediyoruz?
Bir React.js bileşenini test etmek için kullanabileceğimiz birçok strateji vardır:
- Belirli bir olay gönderildiğinde,
props
içindeki belirli bir işlevin çağrıldığını doğrulayabiliriz. - Ayrıca mevcut bileşenin durumu verilen
render
fonksiyonunun sonucunu alabilir ve onu önceden tanımlanmış bir düzen ile eşleştirebiliriz. - Bileşenin alt öğelerinin sayısının beklenen bir miktarla eşleşip eşleşmediğini bile kontrol edebiliriz.
Bu stratejileri kullanmak için React.js'deki testlerle çalışmak için kullanışlı olan iki araç kullanacağız: Jest ve Enzyme.
Birim Testleri Oluşturmak için Jest'i Kullanma
Jest, Facebook tarafından oluşturulan ve React.js ile harika bir entegrasyona sahip olan açık kaynaklı bir test çerçevesidir. Jasmine ve Mocha'nın sunduğuna benzer test yürütme için bir komut satırı aracı içerir. Ayrıca neredeyse sıfır konfigürasyonla sahte işlevler oluşturmamıza olanak tanır ve iddiaların okunmasını kolaylaştıran gerçekten güzel bir eşleştiriciler seti sağlar.
Ayrıca, bileşen oluşturma sonucunu kontrol etmemize ve doğrulamamıza yardımcı olan "anlık görüntü testi" adı verilen gerçekten güzel bir özellik sunar. Bir bileşenin ağacını yakalamak için anlık görüntü testini kullanacağız ve onu bir işleme ağacıyla karşılaştırmak için kullanabileceğimiz bir dosyaya kaydedeceğiz (ya da ilk argüman olarak expect
işlevine ne iletsek.)
React.js Bileşenlerini Monte Etmek İçin Enzim Kullanma
Enzyme, React.js bileşen ağaçlarını monte etmek ve aralarında geçiş yapmak için bir mekanizma sağlar. Bu, iddialarımızı yürütmek için kendi özelliklerine ve durumuna ve ayrıca alt öğelerine erişmemize yardımcı olacaktır.
Enzim, bileşen montajı için iki temel işlev sunar: shallow
ve mount
. shallow
işlev belleğe yalnızca kök bileşeni yüklerken, mount
tüm DOM ağacını yükler.
Bir React.js bileşenini monte etmek ve üzerinde iddialar çalıştırmak için Enzyme ve Jest'i birleştireceğiz.
Ortamımızı Kurmak
Bu örneği çalıştırmak için temel yapılandırmaya sahip olan bu depoya bir göz atabilirsiniz.
Aşağıdaki sürümleri kullanıyoruz:
{ "react": "16.0.0", "enzyme": "^2.9.1", "jest": "^21.2.1", "jest-cli": "^21.2.1", "babel-jest": "^21.2.0" }
TDD Kullanarak React.js Bileşenini Oluşturma
İlk adım, enzimin sığ işlevini kullanarak bir React.js Bileşeni oluşturmaya çalışacak başarısız bir test oluşturmaktır.
// MyComponent.test.js import React from 'react'; import { shallow } from 'enzyme'; import MyComponent from './MyComponent'; describe("MyComponent", () => { it("should render my component", () => { const wrapper = shallow(<MyComponent />); }); });
Testi çalıştırdıktan sonra aşağıdaki hatayı alıyoruz:
ReferenceError: MyComponent is not defined.
Ardından, testi geçmek için temel sözdizimini sağlayan bileşeni oluştururuz.
// MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div />; } }
Bir sonraki adımda, bileşenimizin toMatchSnapshot
işlevini kullanarak önceden tanımlanmış bir UI düzeni oluşturduğundan emin olacağız.
Bu yöntemi çağırdıktan sonra, Jest otomatik olarak __snapshots__
klasörüne eklenen [testFileName].snap
adlı bir anlık görüntü dosyası oluşturur.
Bu dosya, bileşen oluşturmamızdan beklediğimiz UI düzenini temsil eder.
Ancak, saf TDD yapmaya çalıştığımıza göre, önce bu dosyayı oluşturmalı ve ardından testin başarısız olması için toMatchSnapshot
işlevini çağırmalıyız.
Jest'in bu düzeni temsil etmek için hangi formatı kullandığını bilmediğimiz için bu biraz kafa karıştırıcı gelebilir.
Önce toMatchSnapshot
işlevini yürütmek ve sonucu anlık görüntü dosyasında görmek isteyebilirsiniz ve bu geçerli bir seçenektir. Ancak, gerçekten saf TDD kullanmak istiyorsak, anlık görüntü dosyalarının nasıl yapılandırıldığını öğrenmemiz gerekir.
Anlık görüntü dosyası, testin adıyla eşleşen bir düzen içerir. Bu, testimizin şu forma sahip olması durumunda:
desc("ComponentA" () => { it("should do something", () => { … } });
Bunu export bölümünde belirtmeliyiz: Component A should do something 1
.
Anlık görüntü testi hakkında daha fazla bilgiyi buradan okuyabilirsiniz.
Bu yüzden önce MyComponent.test.js.snap
dosyasını oluşturuyoruz.
//__snapshots__/MyComponent.test.js.snap exports[`MyComponent should render initial layout 1`] = ` Array [ <div> <input type="text" /> </div>, ] `;
Ardından, anlık görüntünün bileşen alt öğeleriyle eşleşip eşleşmediğini kontrol edecek birim testi oluştururuz.

// MyComponent.test.js ... it("should render initial layout", () => { // when const component = shallow(<MyComponent />); // then expect(component.getElements()).toMatchSnapshot(); }); ...
components.getElements
öğesini render yönteminin sonucu olarak düşünebiliriz.
Anlık görüntü dosyasına karşı doğrulamayı çalıştırmak için bu öğeleri expect
yöntemine geçiriyoruz.
Testi gerçekleştirdikten sonra aşağıdaki hatayı alıyoruz:
Received value does not match stored snapshot 1. Expected: - Array [ <div> <input type="text” /> </div>, ] Actual: + Array []
Jest, component.getElements
sonucunun anlık görüntüyle eşleşmediğini söylüyor. Bu nedenle, MyComponent
içine girdi öğesini ekleyerek bu testi geçiyoruz.
// MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div><input type="text" /></div>; } }
Sonraki adım, değeri değiştiğinde bir işlevi yürüterek input
işlevsellik eklemektir. Bunu onChange
özelliğinde bir fonksiyon belirleyerek yapıyoruz.
Testin başarısız olması için önce anlık görüntüyü değiştirmemiz gerekiyor.
//__snapshots__/MyComponent.test.js.snap exports[`MyComponent should render initial layout 1`] = ` Array [ <div> <input onChange={[Function]} type="text" /> </div>, ] `;
Önce anlık görüntüyü değiştirmenin bir dezavantajı, sahne öğelerinin (veya niteliklerin) sırasının önemli olmasıdır.
Jest, anlık görüntüye göre doğrulamadan önce expect
işlevinde alınan aksesuarları alfabetik olarak sıralayacaktır. O halde bunları bu sırayla belirtmeliyiz.
Testi gerçekleştirdikten sonra aşağıdaki hatayı alıyoruz:
Received value does not match stored snapshot 1. Expected: - Array [ <div> onChange={[Function]} <input type="text”/> </div>, ] Actual: + Array [ <div> <input type=”text” /> </div>, ]
Bu testi geçmek için onChange
boş bir fonksiyon sağlayabiliriz.
// MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div><input onChange={() => {}} type="text" /></div>; } }
Ardından, onChange
olayı gönderildikten sonra bileşenin durumunun değiştiğinden emin oluruz.
Bunu yapmak için, kullanıcı arayüzünde gerçek bir olayı taklit etmek için bir olay ileterek girişte onChange
işlevini çağıracak yeni bir birim testi oluşturuyoruz.
Ardından, bileşen durumunun input
adında bir anahtar içerdiğini doğrularız.
// MyComponent.test.js ... it("should create an entry in component state", () => { // given const component = shallow(<MyComponent />); const form = component.find('input'); // when form.props().onChange({target: { name: 'myName', value: 'myValue' }}); // then expect(component.state('input')).toBeDefined(); });
Şimdi aşağıdaki hatayı alıyoruz.
Expected value to be defined, instead received undefined
Bu, bileşenin durumda input
adlı bir özelliği olmadığını gösterir.
Bu girişi bileşenin durumuna ayarlayarak test geçişini sağlıyoruz.
// MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div><input onChange={(event) => {this.setState({input: ''})}} type="text" /></div>; } }
Ardından, yeni durum girişinde bir değerin ayarlandığından emin olmamız gerekiyor. Bu değeri olaydan alacağız.
Öyleyse, durumun bu değeri içerdiğinden emin olan bir test oluşturalım.
// MyComponent.test.js ... it("should create an entry in component state with the event value", () => { // given const component = shallow(<MyComponent />); const form = component.find('input'); // when form.props().onChange({target: { name: 'myName', value: 'myValue' }}); // then expect(component.state('input')).toEqual('myValue'); }); ~~~ Not surprisingly, we get the following error. ~~ Expected value to equal: "myValue" Received: ""
Son olarak eventten gelen değeri alıp giriş değeri olarak ayarlayarak bu testi geçiyoruz.
// MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { render() { return <div><input onChange={(event) => { this.setState({input: event.target.value})}} type="text" /></div>; } }
Tüm testlerin geçtiğinden emin olduktan sonra kodumuzu yeniden düzenleyebiliriz.
onChange
özelliğinde geçirilen işlevi updateState
adlı yeni bir işleve çıkartabiliriz.
// MyComponent.js import React from 'react'; export default class MyComponent extends React.Component { updateState(event) { this.setState({ input: event.target.value }); } render() { return <div><input onChange={this.updateState.bind(this)} type="text" /></div>; } }
Artık TDD kullanılarak oluşturulmuş basit bir React.js bileşenimiz var.
Özet
Bu örnekte, testleri geçmek ve başarısız olmak için mümkün olan en az kodu yazarak her adımı takip ederek saf TDD kullanmaya çalıştık.
Adımlardan bazıları gereksiz görünebilir ve onları atlamak için cazip gelebiliriz. Ancak, herhangi bir adımı atladığımızda, TDD'nin daha az saf bir sürümünü kullanırız.
Daha az katı bir TDD süreci kullanmak da geçerlidir ve gayet iyi çalışabilir.
Size tavsiyem, herhangi bir adımı atlamamanız ve zorlanırsanız kendinizi kötü hissetmemenizdir. TDD, ustalaşması kolay olmayan bir tekniktir, ancak kesinlikle yapmaya değer.
TDD ve ilgili davranışa dayalı geliştirme (BDD) hakkında daha fazla bilgi edinmek istiyorsanız, Toptaler Ryan Wilcox'un yazdığı Patronunuz TDD'yi Takdir Etmeyecektir.