How to Integration Test (Versi Laravel)

Akbar M. Adhatama
Javan Cipta Solusi
Published in
5 min readJan 20, 2017
Westeros wants no bug, but no bugs mean no codes

“Quality is not an act, it is a habit.” — Aristotle

Resolusi 2017 di Javan adalah meningkatkan quality software yang kami produksi. Salah satu yang dilakukan divisi Dev kami adalah mewajibkan software yang dideliver memiliki acceptable-coverage automated test.

Tulisan ini sebenarnya adalah materi yang akan di workshop kan yang kemudian berubah jadi sebuah tulisan. Part pertama membahas opinionated tehnik integration test pada Laravel.

Studi Kasus

Twitter Clone

Features:
- Create account
- Post a tweet
- View another’s user tweets
- Follow another user
- View my timeline tweets
- View my own tweet list

Macam-macam test

Mari kita sepahamkan dulu arti dari banyak definisi yang beredar seputar automated test.

Unit test: programmer. PHPUnit, JUnit
ciri-ciri:

- Isolated. Kita ngetest method dengan mengabaikan hubungannya dengan class lain (dengan Mock) atau external resources (database, third party service).
- Fastest execution time karena isolated tadi.
- Sulit karena cenderung harus banyak ngeMock.

Integration test: programmer. PHPUnit, JUnit
ciri-ciri:
- Tidak isolated, bisa berhubungan dengan external resources
- Lebih lama execution time daripada unit test
- Cenderung lebih mudah dibuat daripada unit test

Functional / End to End test / Acceptance test: programmer, tester. Selenium
ciri-ciri:
- Ngetest bagaikan kita ngetest seperti user yang menggunakan browser
- Waktu Eksekusi sangat lama

Mock: Membuat semacam fake method/function untuk mensimulasikan suatu method/function dengan output yang kita define sendiri.

Contoh macam-macam test

Andaikan kita punya class Post dengan method Store seperti dibawah

Bagaimana cara kita ngetest jika menggunakan:

Unit test
Menurutku percuma kita ngetest itu dengan pendekatan unit test karena Post itu bergantung pada external resources yaitu database, maka kita harus ngemock Post.
Jika kita ngemock Post, maka kita define output dari Post itu apa sehingga tidak mencerminkan fungsionalitasi sebenarnya method tersebut.

Integration test
Panggil method store($title, $content) dengan params $title dan $content yang telah kita define.
Method store() akan create row baru di table post sehingga kita bisa memvalidasi apakah ada data di table postdengan title yang telah kita define tadi.

Functional / End to End / Acceptance test
Harus masuk ke halaman create Post lalu bikin script pakai selenium untuk ngisi form, kita define isi dari field title dan content.
Setelah klik submit, validasi dengan script selenium apakah masuk ke halaman List Post dan menampilkan tulisan `Create post success!`

Kali ini kita akan fokus hanya pada Integration Test

Mindset

  1. Programmer harus bertanggungjawab terhadap kode yang ia buat.
  2. Maka dari itu, ia buat test (Integration test) untuk memastikan bahwa kode yang dia buat berjalan baik.
  3. Test (Integration test) yang dia buat bukan untuk mencari bug.

Aturan Test

  • Test harus mudah dibaca
  • Fitur harus bisa diketahui kegunaannya hanya dari membaca test
  • Wajib menggunakan 3 Step untuk readibility
    - Setup
    - Exercise
    - Verify

Studi Kasus

Jika disuruh membuat twitter clone dengan fitur create a tweet, gimana cara bikin test untuk fitur itu?

Pertama, bikin fiturnya dulu

Ada dua pendekatan untuk ngetest fitur create tweet

  1. Test method createTweet($tweet) nya saja
  2. Panggil routes nya method store()

Pendekatan 1

  • Pada bagian Setup, test menyiapkan data yang dibutuhkan untuk test agar bisa ditest. Dalam hal ini, untuk bisa membuat tweet, maka perlu ada user nya dulu.
  • Step Exercise adalah memanggil method yang ingin ditest. Params dari method yang ingin ditest kita define sendiri.
  • Lalu pada step Verify, kita validasi apakah method yang kita panggil tadi sudah melakukan tugasnya dengan benar. Dalam hal ini, ketika method createTweet() dipanggil, maka seharusnya terdapat data di database dengan value My tweet! seperti yang telah kita define sebelumnya.

As simple as that.

Pendekatan 2

  • Pada pendekatan 1, ada masalah yang bisa timbul jika kita hanya ngetest method createTweet() nya saja, yaitu ketika user belum melakukan login.
    Dan juga tanpa login, fitur create tweet kita belum mencerminkan fungsi sebenarnya.
  • Pada step Exercise, Laravel punya $this->actionAs($user) untuk mensimulasikan login sebagai $user dan $this->call() yang bisa digunakan untuk memanggil suatu route beserta paramsnya jika ada.
  • $response bisa digunakan untuk ngetest http status atau redirect kemana setelah kita ngakses route(‘tweet.store’) .
    $this->assertEquals(302, $response->status())
    $this->assertRedirectedTo(route(‘tweet.create’))

Very simple

Kesimpulan

  • Ada 2 pendekatan yang bisa dilakukan untuk melakukan integration test, memanggil method yang ingin ditest dan memanggil route dari method controller yang ingin kita test
  • Untuk kasus create dan juga kasus yang terdapat perubahan data (update, destroy) pada method yang ingin kita test, pendekatan 2 lebih simple dan bisa mencover fitur lebih real.
  • Tapi ada kasus dimana pendekatan 2 sulit dilakukan, yaitu pada next studi kasus

Studi kasus 2

Membuat fitur show tweets dari suatu user

Buat dulu fiturnya

Anggap kita melakukan test Pendekatan 2, maka:

  • Harapannya kita bisa ngevalidasi variable $tweets yang dikirim ke view(‘users.showTweets’) pada hasil $response
  • Tapi $response dari $this->call() tidak bisa untuk mendapatkan hanya variable $tweets yang dikirim ke view users.showTweets. $response hanya bisa mendapatkan body content dari request call yang dilakukan, yaitu full html pada view(‘users.showTweets’). Tentu sulit dilakukan validasi pada html tersebut.
  • Maka dari itu, perlu kita gunakan Pendekatan 1, yaitu kita panggil $user->getTweets() untuk ditest. Jadi test kita ubah jadi seperti ini:

Simple.

Setup database

  • Saat ini baru dicoba untuk db mysql dan sqlite saja.
  • Database untuk test sebaiknya disamakan dengan database yang digunakan aplikasi untuk menghindari perbedaan hasil test dengan yang di aplikasi.
  • Karena Gitlab CI Javan baru support Mysql, maka kita gunakan Mysql.

4 Hal yang perlu disetting configurasinya untuk setup database

  1. Tambahkan config database khusus test pada .env
DB_CONNECTION_TEST=mysql_test
DB_HOST_TEST=127.0.0.1
DB_PORT_TEST=8889
DB_DATABASE_TEST=phpunit-test
DB_USERNAME_TEST=root
DB_PASSWORD_TEST=root

2. Tambahkan connection baru pada conf/database.php dengan menggunakan config step pertama

'mysql_test' => [
'driver' => 'mysql',
'host' => env('DB_HOST_TEST', 'localhost'),
'port' => env('DB_PORT_TEST', '3306'),
'database' => env('DB_DATABASE_TEST', 'forge'),
'username' => env('DB_USERNAME_TEST', 'forge'),
'password' => env('DB_PASSWORD_TEST', ''),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],

3. Gunakan koneksi database baru pada config phpunit.xml dengan menambahkan:

<env name=”DB_CONNECTION” value=”mysql_test”/>

4. Jalankan migrate menggunakan koneksi database yang khusus mysql test.
Migrate hanya perlu dijalankan sekali di awal.
Ini untuk menghindari dilakukan migrate pada setiap test case sehingga memperlama proses test.
Untuk memastikan data tetap fresh seperti di kondisi setelah migrate pertama kali pada setiap test case, gunakan traits DatabaseTransactions pada setiap class test.

php artisan migrate --seed --database=mysql_test

Untuk contoh-contoh lebih lanjut bisa dilihat di:

Penutup

Harapannya dengan tulisan ini, pengguna Laravel bisa lebih terbayang untuk bagaimana membuat test yang simple dan mudah dibaca namun tetap mampu melakukan test terhadap suatu fitur dengan tepat.

Tulisan ini akan terus diupdate mengikuti perkembangan knowledge dan kebutuhan berdasarkan test-test yang kami terapkan dalam project kami.

Akan ada next tulisan yang menggunakan Grails. So stay tuned!

--

--