Web Automation Testing Part 2

indrak
10 min readMar 8, 2021

--

Di artikel sebelumnya sudah coba dibahas tentang unit-test dan coverage-test menggunakan Jest untuk menguji suatu function. Di artikel ini akan coba dibahas unit-test dan coverage-test di suatu component.

Sebenarnya testing di component tidak jauh berbeda dengan testing untuk function, perbedaannya disini terdapat proses inspeksi DOM untuk keperluan pengujian. Ayo langsung saja kita bahas unit-test, coverage-test & tambahan snapshot-test di suatu component. Karena istilah unit-test & coverage-test sudah dibahas sebelumnya, disini hanya dijelaskan apa itu snapshot-test.

Untuk memudahkan memahami kita akan membuat satu project sederhana menggunakan framework yang bisa digunakan untuk membuat component.

freepik image — Cartoon vector created by upklyak —

Snapshot-Test

Snapshot-test adalah teknik yang sangat berguna untuk memastikan UI yang kita buat tidak berubah secara tiba-tiba tanpa kita harapkan. Ketika perubahan itu memang sesuai yang diharapkan, kita bisa meng-update snapshot yang ada. Namun ketika perubahan itu tidak diharapkan, bisa digunakan sebagai acuan untuk perbaikan.

OK, cukup teorinya. Sekarang kita coba lakukan percobaan untuk lebih memahami tentang unit-test, coverage-test, & snapshot-test di suatu component. untuk melakukan teknik-teknik testing tersebut, kita akan membuat project dengan menggunakan svelte, testing-library & tentu saja jest.

Experiment

Kita akan coba setup suatu project dan melakukan percobaan-percobaan di project ini. Ikuti langkah-langkah di bawah ini:

Install Svelte

  • Clone svelte project dengan eksekusi
npx degit sveltejs/template component-testing
  • Pindah ke folder svelte dan eksekusi npm i untuk instalasi package yang dibutuhkan.
  • Kemudian eksekusi perintah npm run dev untuk mengecek apakah project ini sudah bisa dijalankan dengan benar. . Kalau sudah muncul tampilan dibawah ini berarti project sudah bisa digunakan:

Install Unit-Test Tool

  • Install Jest & Testing-Library
npm i -D @testing-library/svelte @testing-library/jest-dom jest svelte-jester babel-jest @babel/preset-env
  • Tambahkan script di package.json
{
"scripts": {
"test": "jest src",
"test-watch": "jest src --watchAll",
"test-cov": "jest src --coverage"
}
}
  • Buat file jest.config.js
module.exports = {
transform: {
"^.+\\.js$": "babel-jest",
"^.+\\.svelte$": [
"svelte-jester": {
debug: true,
}
]
},
bail: false,
verbose: true,
moduleFileExtensions: [
"js",
"svelte",
"json"
],
setupFilesAfterEnv: [
"@testing-library/jest-dom/extend-expect"
],
testPathIgnorePatterns: [
"/node_modules/",
"/build/"
],
coveragePathIgnorePatterns: [
"/node_modules/",
"/build/"
],
coverageProvider: "babel",
collectCoverageFrom: [
"src/**/*.svelte"
],
coverageThreshold: {
"./src/components/": {
"branches": 80,
"functions": 100,
"lines": 100,
"statements": 100
},
},
coverageReporters: ["json", "html", "text", "clover"],
}
  • Buat file babel.config.js
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: {
node: "current"
}
}
]
]
};

Create Component

component button yang akan kita buat adalah button yang memiliki 3 jenis UI style: primary, secondary & transparent

component bisa diberi action click

memiliki state loading & disable, dimana kalau terjadi state loading atau disable maka button tidak bisa di-klik

  • Buat folder components & Buat file button.svelte di dalam folder components tersebut.
<script>
import { onMount } from "svelte";
import { type } from "../constants";
export let data = {
title: null,
type: null,
click: null,
disabled: false,
loading: false,
};
$: onClick = data.loading || data.disabled ? null : data.click;
$: isDisabled = data.disabled === true;
$: isLoading = data.loading === true;
let title;
let style;
onMount(async () => {
title = (await data.title) ? data.title : "";
style = await `button ${data.type ? data.type : type.primary}`;
});
</script>
<style>
.button {
font-family: Muli;
font-size: 14px;
font-weight: bold;
text-decoration: none;
border: none;
border-radius: 500px;
margin: 16px auto;
width: 100%;
cursor: pointer;
outline: none;
display: block;
padding: 13px 10px;
text-align: center;
box-sizing: border-box;
}
button:disabled {
color: #e1e1ed !important;
background: #9ca4ac !important;
pointer-events: none !important;
}
button.disabled {
color: #e1e1ed !important;
background: #9ca4ac !important;
pointer-events: none !important;
}
.button.primary {
background: red;
border: 1px solid white;
color: white;
}
.button.secondary {
background: white;
border: 1px solid red;
color: red;
}
.button.transparent {
background: transparent;
border: none;
color: red;
}
</style>
<button
class={style}
disabled={isDisabled}
on:click={onClick}
data-testid="button-tid">
{#if !isLoading}{title}{:else}Loading...{/if}
</button>
  • Buat file constants.js yang dibutuhan file button.svelte
export const type = {
primary: "primary",
secondary: "secondary",
transparent: "transparent",
};
  • Update content App.svelte untuk memanggil component button
<script>
import Button from "./components/button.svelte";
export let data = {
title: "Button",
type: null,
click: null,
disabled: false,
loading: false,
};
</script>
<p style="text-align: center;">This is Button Page</p>
<div style="align: center" data-testid="app-tid">
<Button {data} />
</div>

Struktur akhir folder & file experiment yang kita buat adalah

Persiapan kita sudah selesai. Ketika kita eksekusi npm run dev dan kita akses via browser, maka tampilan akan seperti berikut ini:

Sudah sesuai dengan component button yang telah kita buat.

Selanjutnya kita akan mencoba melakukan pembuatan unit-test untuk menguji component button diatas terbebas dari bug.

Unit-Test Experiment

OK, sekarang kita sampai ke tahap experiment untuk lebih memahami cara kerja Jest melakukan unit-test sampai dengan snapshot-test. Pengujian dimulai dengan ekseksusi script npm run test yang tentu saja akan menghasilkan “error No test found, exiting with code 1” karena kita memang belum membuat unit-test untuk component button.js. Mari kita buat unit-test secara bertahap.

Di dalam folder components, buat file button.test.js

import { render, fireEvent, waitFor } from '@testing-library/svelte';
import { type } from "../constants"
import Button from './button.svelte';
const testId = "button-tid";
const buttonData = {
title: null,
type: null,
click: null,
disabled: false,
loading: false,
}
describe('Button', () => {
it('default', async () => {
expect.hasAssertions();
const mockClick = jest.fn();
buttonData.click = mockClick;
const { getByTestId } = render(Button, { props: { data: buttonData } });
let button;
await waitFor(() => {
button = getByTestId(testId);
expect(button).toHaveTextContent('');
expect(button.classList.contains(type.primary)).toBe(true);
fireEvent.click(button);
})
expect(mockClick).toHaveBeenCalledTimes(1);
});
});

Pada unit-test ini output yang diharapkan adalah saat Button di-render dengan props minimum akan menghasilkan component Button dengan kriteria:

  • memiliki style primary button dengan pengujian expect(button.classList.contains(type.primary)).toBe(true);
  • memiliki empty title text dengan pengujian expect(button).toHaveTextContent(‘’);
  • bisa di-klik dengan pengujian expect(mockClick).toHaveBeenCalledTimes(1);

Kemudian perlu kita buat unit-test untuk App.svelte juga. Buat file App.test.js didalam folder src

import { render, waitFor } from '@testing-library/svelte';
import App from './App.svelte';
import { type } from "./constants"
const buttonData = {
title: "Button",
type: null,
click: null,
disabled: false,
loading: false,
};
describe('App', () => {
it('default', async () => {
expect.hasAssertions();
const { container } = render(App, { props: { data: buttonData } });
await waitFor(() => { expect(container.firstChild.children[0]).toHaveTextContent("This is Button Page"); expect(container.firstChild.children[1].firstChild.classList.contains(type.primary)).toBe(true);
})
});
});

Di unit-test ini cukup simple kita hanya melakukan pengujian apakah di component ini terdapat text “This is Button Page” dan button yang dipanggil dari component button.js

Lalu coba eksekusi perintah npm run test. Hasil output-nya akan seperti ini:

Berhasil!!!

Unit-test untuk component sudah kita buat dan berhasil melakukan verifikasi untuk component yang telah kita buat. Sebelum kita melanjutkan ke coverage-test, di hasil test diatas, ada keterangan Snapshots: 0 total yang berarti kita belum melakukan testing untuk UI. Coba kita buktikan, ganti bagian css di component button.js pada bagian primary menjadi warna hijau.

.button.primary {
background: green;
border: 1px solid white;
color: white;
}

Kemudian coba jalankan kembali npm run test dan unit-test yang kita buat masih passed. Tentu saja hal seperti ini tidak kita inginkan. Ketika terdapat perubahan UI, seharusnya unit-test juga melaporkan bahwa pada component terdapat perubahan, untuk selajutnya kita akan putuskan menerima/menolak perubahan UI tersebut. Hal ini akan sangat berpengaruh terhadap konsistensi component terkait. Untuk membuat unit-test kita melakukan pengecekan terhadap tampilan UI, kita bisa menambahkan kriteria expect(container).toMatchSnapshot() di test-case kita.

Update file button.test.js menjadi seperti dibawah ini:

import { render, fireEvent, waitFor } from '@testing-library/svelte';
import { type } from "../constants"
import Button from './button.svelte';
const testId = "button-tid";
const buttonData = {
title: null,
type: null,
click: null,
disabled: false,
loading: false,
}
describe('Button', () => {
it('default', async () => {
expect.hasAssertions();
const mockClick = jest.fn();
buttonData.click = mockClick;
const { getByTestId } = render(Button, { props: { data: buttonData } });
let button;
await waitFor(() => {
button = getByTestId(testId);
expect(button).toHaveTextContent('')
expect(button.classList.contains(type.primary)).toBe(true);
expect(button).toMatchSnapshot(); // capture snapshot
fireEvent.click(button);
})
expect(mockClick).toHaveBeenCalledTimes(1);
});
});

dan file App.test.js seperti dibawah ini:

import { render, waitFor } from '@testing-library/svelte';
import App from './App.svelte';
import { type } from "./constants"
const buttonData = {
title: "Button",
type: null,
click: null,
disabled: false,
loading: false,
};
describe('App', () => {
it('default', async () => {
expect.hasAssertions();
const { container } = render(App, { props: { data: buttonData } });
await waitFor(() => {
expect(container.firstChild.children[0]).toHaveTextContent("This is Button Page");
expect(container.firstChild.children[1].firstChild.classList.contains(type.primary)).toBe(true);
expect(container).toMatchSnapshot(); // capture snapshot
})
});
});

Lalu kita coba jalankan kembali npm run test, dan hasilnya bisa dilihat sebagai berikut:

Bisa dilihat bahwa pada hasil unit-test kali ini terdapat 2 snapshot yang di-generate. Snapshot ini akan digunakan sebagai acuan pengujian snapshot-test selanjutnya. Coba kita ganti kembali css button primary kita menjadi warna merah.

.button.primary {
background: red;
border: 1px solid white;
color: white;
}

Setelah dicoba dieksekusi lagi npm run test maka akan terjadi error dibawah ini:

Bisa kita lihat, jika terjadi perubahan css, dan Jest memberikan alarm kalau snapshot-test yang kita lakukan menghasilkan status failed di unit-test button.test.js. Kemudian bisa kita lihat juga di App.test.jssebagai component yang menggunakan component button — terdapat error juga karena snapshot yang dihasilkan berubah dari acuan sebelumnya.

Ketika snapshot-test yang kita lakukan menghasilkan status failed, perlu diputuskan apakah perubahan ini diterima atau tidak. Jika tidak, maka kita harus memperbaikinya. Jika diterima, maka snapshot ini bisa kita update, untuk selanjutnya akan digunakan sebagai acuan snapshot-test selanjutnya. Untuk melakukan update kita harus memberikan argumen -u saat melakukan testing. sehingga perintah yang kan kita eksekusi adalah npm run test — -u

Setelah kita eksekusi, maka hasilnya akan seperti berikut ini:

Berhasil!!!

Sejauh ini kita sudah berhasil melakukan unit-test & snapshot-test. Tapi apakah unit-test ini sudah memiliki coverage yang baik? mari kita buktikan. Seperti biasa coba kita jalankan perintah npm run test-cov, dan hasilnya adalah sebagai berikut:

Ternyata di component button.js, unit-test kita masih kurang. Ayo kita lanjutkan experiment ini.

Untuk menambahkan unit-test ini, kita perlu analisa kondisi-kondisi apa saja yang mungkin terjadi di component button. Bisa dilihat juga bagian Uncovered Lines & html report sebagai acuan penambahan. Cara membaca report tersebut bisa dilihat di artikel ini.

Tambahkan unit-test dibawah ini di file button.test.js untuk membuat coverage-test nya menjadi 100%

import { render, fireEvent, waitFor } from '@testing-library/svelte';
import { type } from "../constants"
import Button from './button.svelte';
const testId = "button-tid";
const buttonData = {
title: null,
type: null,
click: null,
disabled: false,
loading: false,
}
describe('Button', () => {
it('default', async () => {
expect.hasAssertions();
const mockClick = jest.fn();
buttonData.click = mockClick;
const { getByTestId } = render(Button, { props: { data: buttonData } });
let button;
await waitFor(() => {
button = getByTestId(testId);
expect(button).toHaveTextContent('')
expect(button.classList.contains(type.primary)).toBe(true);
expect(button).toMatchSnapshot();
fireEvent.click(button);
})
expect(mockClick).toHaveBeenCalledTimes(1);
});
it('undefined', async () => {
expect.hasAssertions();
const mockClick = jest.fn();
buttonData.click = mockClick;
const { getByTestId } = render(Button);
await waitFor(() => {
const button = getByTestId(testId);
expect(button).toHaveTextContent('')
expect(button.classList.contains("button")).toBe(true);
expect(button).toMatchSnapshot();
fireEvent.click(button);
})
expect(mockClick).toHaveBeenCalledTimes(0);
});
it('title', async () => {
expect.hasAssertions();
const { getByTestId } = render(Button, { props: { data: { ...buttonData, title: "Button Title" } } });
await waitFor(() => {
const button = getByTestId(testId);
expect(button).toHaveTextContent("Button Title");
expect(button).toMatchSnapshot();
})
});
it('loading', async () => {
expect.hasAssertions();
const mockClick = jest.fn();
buttonData.click = mockClick;
const { getByTestId } = render(Button, { props: { data: { ...buttonData, loading: true } } });
let button;
await waitFor(() => {
button = getByTestId(testId);
expect(button).toHaveTextContent("Loading...")
expect(button).toMatchSnapshot();
fireEvent.click(button);
})
expect(mockClick).toHaveBeenCalledTimes(0);
});
it('disabled', async () => {
expect.hasAssertions();
const mockClick = jest.fn();
buttonData.click = mockClick;
const { getByTestId } = render(Button, { props: { data: { ...buttonData, disabled: true } } });
let button
await waitFor(() => {
button = getByTestId(testId);
expect(button).toHaveAttribute('disabled')
expect(button).toMatchSnapshot();
fireEvent.click(button);
})
expect(mockClick).toHaveBeenCalledTimes(0);
})

it('primary', async () => {
expect.hasAssertions();
const { getByTestId } = render(Button, { props: { data: { ...buttonData, type: type.primary } } });
await waitFor(() => {
const button = getByTestId(testId)
expect(button.classList.contains(type.primary)).toBe(true);
expect(button).toMatchSnapshot();
})
})
it('secondary', async () => {
expect.hasAssertions();
const { getByTestId } = await render(Button, { props: { data: { ...buttonData, type: type.secondary } } });
await waitFor(() => {
const button = getByTestId(testId)
expect(button.classList.contains(type.secondary)).toBe(true);
expect(button).toMatchSnapshot();
})
})
it('transparent', async () => {
expect.hasAssertions();
const { getByTestId } = await render(Button, { props: { data: { ...buttonData, type: type.transparent } } });
await waitFor(() => {
const button = getByTestId(testId)
expect(button.classList.contains(type.transparent)).toBe(true);
expect(button).toMatchSnapshot();
})
})
});

Setelah kita eksekusi dengan penambahan unit-test diatas, bisa dilihat hasil coverage sudah mencapai 100%

Berhasil!!!

Akhirnya kita bisa membuat sebuah component dengan unit-test, snapshot-test, & coverage-test yang mencapai 100%. Sekarang kita lebih percaya diri merilis aplikasi ini ke production.

Tapi ngomong-ngomong, di component button ini kita ada beberapa state yang kita uji. Misal, ketika kita menggunakan type=primary, button harus bisa memberikan snapshot yang primary juga. Dengan percobaan unit-test diatas kita memang bisa mengujinya secara otomatis dengan script, tapi kita tidak bisa melihat component ini secara visual yang langsung dilihat oleh mata kita.

Disini kita memiliki kebutuhan untuk bisa melihat kalau ada state loading tampilannya jadi gimana sih? atau juga kalau lagi disable tampilan button jadi gimana juga? Nah, untuk mengakomodir kebutuhan ini, kita akan menuju experiment selanjutnya yang lebih menantang. Kita akan bermain-main dengan Storybook.

Dengan storybook ini kita bisa membuat component yang kita buat menjadi suatu library yang bisa dipakai di project lain.

Kita akan bahas experiment dengan storybook ini di pembahasan terpisah.

Source code bisa dilihat di sini.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

indrak
indrak

Written by indrak

0 Followers

always learning

No responses yet

Write a response