08 April, 2023 854 görüntülenme

React Uygulamasına Test Yazalım

React Uygulamasına Test Yazalım

Uzun zamandır beklenen bir yazı dizisiydi. Aslında ben de test üzerine kendimi geliştirmek için ufakta olsa birkaç bir şey yapmak istiyordum. Bu yazı da anlatacağım için benim için bir bahane olmuş oldu. Ee bildiğiniz gibi eğer öğrenmek istediğin bir şey varsa onu birisine anlatmak gerek. Gerek ki daha iyi öğrenesin... O yüzden karşınızdayım :)

Yazıya başlamadan önce hemen backlink geçeyim. Serinin 1. yazısını okumayanlara buraya alalım. React Uygulamasına Test Yazalım #1

Test nedir, unit test nedir, integration test nedir gibi kısımlara giriş yapmadan direk konunun içine gireceğiz. Daha akılda kalıcı olması için bir proje geliştirdim ve bunu github üzerinden herkese açık bir şekilde paylaştım. Hem bu şekilde sizler de projeyi inceleyebilir ve test konusunu en azından bir noktaya kadar anlayabilirsiniz.

Projeyi İnceleyelim

Aslında projeyi basit tutmaya çalıştım. Bu şekilde odaklanmamız gereken konuya daha iyi bir biçimde kafa yorabiliriz. Proje, basit bir misafir defter uygulaması. Tamamen localde çalışan, api bağlantısı olmayan, birkaç component'ten oluşan bir proje. Projenin canlı halini Guestbook adresinden inceleyebilirsiniz.

Github üzerinden kaynak kodlarına ulaşmak isterseniz de github.com/berat/guestbook adresinden ulaşabilirsiniz. Ayrıca projeyi geliştirirken ekran kaydı alıp youtube üzerinden paylaştım.

Unit Testlerle Başlayalım

Aslında tam olarak hangi durumları keşfetebilirim pek bilemedim. Ama aklıma gelenler sırasıyla şunlar;

gibi sorulara cevap almaya çalıştım. Tabii yazdığım test yine basit kalıyor. Bunun daha ilerisi redux bağlantısı ve api bağlantısı olan bir uygulamada mock datalar ile test yazmak ve store değişikliğini test etmek olacaktır. Ama tabii bunlar ileri konular. Şimdilik bunlar işimizi görecektir.

Yorumun yazılacağı form component

import { memo, useEffect, useState } from "react";

const INITIAL_STATE = {
  name: "",
  comment: "",
};

const Form = ({ setComments }) => {
  const [values, setValues] = useState(INITIAL_STATE);
  const [buttonDisabled, setButtonDisabled] = useState(true);

  const handleChange = (event) => {
    const { name, value } = event.target;
    setValues({ ...values, [name]: value });
  };

  useEffect(() => {
    const getValues = Object.values(values);

    const isEmpty = getValues.some((item) => item === "");

    if (isEmpty) setButtonDisabled(true);
    else setButtonDisabled(false);
  }, [values]);

  const onSubmit = () => {
    setComments((prev) => [
      ...prev,
      {
        id: Math.floor(Math.random() * 999999),
        name: values.name,
        comment: values.comment,
      },
    ]);
    // reset values
    setValues(INITIAL_STATE);
  };

  return (
    <div id="form">
      <input
        type="text"
        placeholder="Adınız"
        name="name"
        value={values.name}
        onChange={handleChange}
      />
      <textarea
        rows={2}
        placeholder="Mesajınız"
        name="comment"
        value={values.comment}
        onChange={handleChange}
      ></textarea>
      <button onClick={onSubmit} disabled={buttonDisabled}>
        Gönder
      </button>
    </div>
  );
};

export default memo(Form);

Burada bir input bir textarea bir de button olan basit bir component. Bu componentin çalışması için de gerekli olan bazı fonksiyonlar var. Bunları özetlemek gerekirse şöyle;

Yorumun yazılacağı form component testi

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Form from "./Form";

describe("should render", () => {
  test("input", () => {
    render(<Form />);

    const input = screen.getByPlaceholderText("Adınız");

    expect(input).toBeInTheDocument();
  });
  test("textarea", () => {
    render(<Form />);

    const textarea = screen.getByPlaceholderText("Mesajınız");

    expect(textarea).toBeInTheDocument();
  });
  test("button", () => {
    render(<Form />);

    const button = screen.getByRole("button");

    expect(button).toBeInTheDocument();
  });
});

test("case button dissable props", () => {
  render(<Form />);

  // get elements
  const input = screen.getByPlaceholderText("Adınız");
  const textarea = screen.getByPlaceholderText("Mesajınız");
  const button = screen.getByRole("button");

  expect(button).toBeDisabled();

  userEvent.type(input, "test");
  userEvent.type(textarea, "test");

  expect(button).not.toBeDisabled();
});
Test caselerin açıklamalarına takılmayın. Siz daha açıklayıcı ve yerinde ifadeler kullanın :)

Burada ise adım adım şöyle ilerliyorum;

userEvent kütüphanesi ile input değerini girebiliyor, trigger yaratabiliyor ya da herhangi bir şey upload edebiliyorsun. Detaylı kullanım için şu sayfayı inceleyebilirsiniz.

Diğer component'i de ayrıca yazmak istemedim. Çünkü birbirine benzeyen yapı olduğu için tek component üzerinden hepsini anlatmış oldum. İncelemek isterseniz github üzerinden detaylı bir şekilde inceleyebilirsiniz.

Şimdi sırada Integration Test

Bence tek başına unit testler yeterli olmuyor. Çünkü componentler birbirleriyle bağlantılı olabiliyor. Bu yüzden integration test ile uçtan uca bir test yazmak gerekiyor. Benim tercihim burada Cypress oluyor. Çünkü yazımı olsun, sunduğu imkanlar olsun benim için 1-0 öne geçiyor.

Gerekli kurulumları yaptıktan sonra sıra geliyor test yazmaya. Benim testimde 4 adım var. Bunlar;

describe("first test", () => {
  it("post a comment", () => {
    cy.visit("/"); // (1)

    // inputları doldur. (2)
    cy.get('input[type="text"]').type("Berat Bozkurt");
    cy.get("textarea").type("Bu bir deneme yorumdur.");

    // butona tıkla (3)
    cy.get("button").click();

    // yorum gönderildi mi kontrol et. (4)
    cy.get("article p").invoke("text").should("eq", "Bu bir deneme yorumdur.");
  });
});
Tüm kaynaklar ve dökümanlar yazının en altında bulunmaktadır.

Integration test yazma işlemini tamamladıktan sonra ister terminal istersen browser üzerinden test işlemi gerçekleşiyor. Benim tercihim browser üzerinden test işlemi gerçekleştirmek. Çünkü browser üzerinden test işlemi gerçekleştirdiğimde eğer bir hata varsa o hata ile karşılaştığım zaman hata mesajı ile karşılaşıyorum. Bu da benim için daha anlaşılır oluyor.

Sonuç

Aslında test yazmak o kadar da zor olmadığını görüyoruz. Sadece bunun için gerekli zamanı ayırıp case'leri belirlememiz gerekmektedir. Daha sonra ise daha kaliteli ve sağlam bir kod yapısı oluşturmuş oluyoruz. Çünkü case'lerimiz belli. Eğer component ileride bir revize geçirecekse bug yaratma olasılığımız çok ama çok azalıyor. Hem de test coverage ile yazdığımız component'lerin ne kadar test edildiğini görebiliyoruz. Bu da projenin kalitesini arttırıyor.


Yazıyı bitirmeden önce beni aşağıdaki sosyal medya hesaplarımdan takip edebilirsiniz. Aklınıza takılan bir şey olması halinde mesaj ya da olmasına da gerek kalmadan bir selam gönderebilirsiniz.

Kaynakça

Sonraki Yazılar