Test Odaklı React.js Geliştirme: Enzyme ve Jest ile React.js Birim Testi

Yayınlanan: 2022-03-11

Michael 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.

Bir tepki bileşeni oluşturmak için TDD adımları

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.