Frontend Testing — 2
Merhabalar, bugünkü yazımızda frontend testindeki önemli bir konu olan kullanıcı etkileşimlerinden bahsedeceğiz.
Konuya girmeden önce test temellerden ve query’lerden bahsettiğim önceki yazımı okumayanlar aşağıdaki linkten ulaşabilirler…
https://medium.com/appcent/frontend-testing-a6196f06d782
Kullanıcı Etkileşimleri Nedir ? Ne Kullanacağız ?
Bir kullanıcının bir button’a basması, bir input’un içini doldurması, bir klavye tuşuna basması, bir mouse ile herhangi bir yerin üzerine gitmesi ve bir veri değişikliği örnek olarak verilebilir.
Bunun için test tarafında kullanabildiğimiz 2 paket var.
1-) fireEvent 2-) userEvent
userEvent fireEvent’e göre daha gelişmiş bir browser similasyonu sağladığı için biz bu dökümanda userEvent’i kullanacağız.
Ek olarak anlatım kısmımızda hayali bir user oluşturmamızı sağlayacak setup yapısını da kullanmış olacağız.
Bu setup yapısı bize gerçek bir kullanıcının tanımladığı etkileşimler gibi davranan birden fazla ardışık etkileşimin yazılmasına olanak sağlıyor olacak.
O halde ilk örneğimiz ile başlayalım!
App.js
function App() {
const [count,setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
return (
<div>
<h1>{count}</h1>
<button onClick={handleIncrement}>Increment</button>
</div>
);
}
App.test.js
test('should render the element correctly', async () => {
const user = userEvent.setup();
render(<App />);
await user.pointer({
keys: "[MouseLeft]",
target: screen.getByRole("button",{name:"Increment"}),
});
const headingElement = await screen.findByRole("heading");
expect (headingElement).toHaveTextContent("1");
});
İlk örneğimiz için;
Burada basit bir count uygulamasıyla bir mouse event örneği gösterdik.
Icrement text’ine sahip button’ı seçtik ve daha sonra expect ile button bir artırmış mı onu sorguladık.
Ek olarak önemli bir detay da user.pointer asenkron bir yapı olduğu için bunu async-await ile kullanırsak ileri süreçte CD/CD pipeline’larında da sorun yaşama ihtimalimizi ortadan kaldırmış oluyoruz.
Ve işte test sonucumuz…
Daha fazla örnek için aşağıdaki linkten denemeler yapabilirsiniz.
https://testing-library.com/docs/user-event/pointer/#pointer-action
Keyboard eventleri ile devam ediyoruz…
Bu eventler klavyede tuşlara bastığımız eventleri taklit etmemizi sağlıyor.
Hemen ilk örneğimizle açıklayalım.
App.js
function App() {
const [count,setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
return (
<div>
<h1>{count}</h1>
<button onClick={handleIncrement}>Increment</button>
</div>
);
}
App.test.js
test('should render the element correctly', async () => {
const user = userEvent.setup();
render(<App />);
const buttonElement = screen.getByRole("button",{name:"Increment"});
await user.keyboard('[tab]');
await user.keyboard('[enter]');
const headingElement = await screen.findByRole("heading");
expect(buttonElement).toHaveFocus();
expect(headingElement).toHaveTextContent("1");
});
Tab tuşuna basılıp button elementine focus olmasını ve ardından button elementine basılmasını beklediğimiz bir test yaptık. Ve sonuç…
Clipboard Eventleri (copy(), cut() ve paste() )
Text’lerimiz için olmazsa olmaz fonksiyonlarımızı deneyeceğiz.
Aşağıda örneğimizde copy() ve paste() fonksiyonlarımızı kullanacağız.
App.js
function App() {
const [count,setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
const handleCopy = () => {
window.navigator.clipboard.writeText("TestData");
}
return (
<div>
<h1>{count}</h1>
<button onClick={handleCopy}>Copy</button>
<input type="text" />
</div>
);
}
App.test.js
test('should render the element correctly', async () => {
const user = userEvent.setup();
render(<App />);
await user.keyboard('[tab]');
await user.keyboard('[enter]');
const inputElement = screen.getByRole("textbox");
inputElement.focus();
await user.paste();
expect(inputElement).toHaveValue("TestData");
});
Bu örnekte copy button’una tıklanıldığı zaman gerçekten text’i alabiliyor muyuz ve bu text alınıyorsa input’un içerisine başarıyla yapıştırılıyor mu bunu test ettik.
Test sonucumuza göre bu fonksiyonumuz başarıyla çalışıyor.
Utility APIs
Dökümanda en sık kullanılan eventler Utility APIs altında toplanılmış bizde bunlardan bahsedeceğiz.
clear() = Bir input veya textbox’ın içerisini tamamen silmeye yarıyor.
Bunu dökümanda yer alan basit bir örnekle gösterelim.
test('clear', async () => {
render(<textarea defaultValue="Hello, World!" />)
await userEvent.clear(screen.getAllByRole('textbox'))
expect(screen.getByRole('textbox')).toHaveValue('')
});
Burada bir diğer önemli foknsiyonlar ise selectOptions() ve deselectOptions() olmaktadır.
Bunları da tek bir örnek üzerinden gösterelim.
App.js
function App() {
return (
<div>
<select multiple>
<option value="1">Fiat</option>
<option value="2">Opel</option>
<option value="3">Hyundai</option>
</select>
</div>
);
}
App.test.js
test('should render the element correctly', async () => {
const user = userEvent.setup();
render(<App />);
const brands = screen.getByRole("listbox");
await user.selectOptions(brands,["1","Opel"]);
expect (screen.getByRole("option",{name:"Fiat"}).selected).toBe(true);
expect (screen.getByRole("option",{name:"Opel"}).selected).toBe(true);
expect (screen.getByRole("option",{name:"Hyundai"}).selected).toBe(false);
await user.deselectOptions(brands,["1"]);
expect (screen.getByRole("option",{name:"Fiat"}).selected).toBe(false);
});
Burada araba markalarını listelediğimiz bir select option oluşturduk. Ardından bu option’lar üzerinde bir tanesinin value değerini diğerinin de name değerini selectOptions() fonksiyonu içinde belirttik. Devamında expect ile select kontrollerini sorguladık son olarak deselectOption() fonksiyonu ile value’si 1 olan option’ı çıkartıp son bir kontrol yaptık ve sonuç…
Type Event
Bu event input’ların veya textarea’ların içerisine yazılan string ifadenin gidip gitmediğini sorgulamamızı sağlıyor.
Hemen basit bir örnekle gösterelim…
App.js
function App() {
return (
<div>
<input />
</div>
);
}
App.test.js
test('should render the element correctly', async () => {
const user = userEvent.setup();
render(<App />);
const inputElement = screen.getByRole("textbox");
await user.type(inputElement,"Test Data");
expect(inputElement).toHaveValue("Test Data");
});
Son olarak bize büyük bir kolaylık Convenience APIs’den bahsedeceğim.
Convenience APIs
Dökümanda açıklamasında da olduğu gibi temelde yaptığımız pointer() ve keyboard() api’lerine yapılan çağrıların shortcut’lardır. İçerisinde click(), dblClick(), tripleClick(), hover(), unhover(), tab() eventleri yer almaktadır. Daha fazla detay için link bırakıyor ve son örneğimi gerçekleştiriyorum.
https://testing-library.com/docs/user-event/convenience
App.js
function App() {
const handleOnClick = () => {
console.log("I am clicked")
}
return (
<div>
<button onClick={handleOnClick}>Click me</button>
</div>
);
}
App.test.js
test('should click', async () => {
const user = userEvent.setup();
render(<App />);
const buttonElement = screen.getByRole("button",{name:"Click me"});
await user.click(buttonElement);
expect(true).toBe(true);
});
Ve son örneğimizin son sonucu…
Şimdilik bu kadar. Bir sonraki seride daha detay konuları ele alacağız. Okuduğunuz için teşekkürler.