Laravel: Unit Testing dengan SQLite Memory

Lama tidak menulis artikel, hari ini coba untuk membahas tulisan sederhana bagaimana kita membuat unit testing di Laravel dengan menggunakan SQLite di dalam memory. Kenapa harus membuat unit testing? kenapa harus menyimpannya pada SQLite? dan kenapa menggunakan memory? Yuk kita bahas satu per satu ya.

Kenapa membuat Unit Testing?

Sebagai developer atau programmer, kita tentu diharapkan dapat membuat aplikasi yang minim error. Bagaimana agar minim error? tentu kita harus mengenal apa itu error dan bagaimana error itu bisa muncul. Ketika error sudah kita kenali, kita buat kode untuk menghindari error tersebut.

Pekerjaan ini tentu memakan waktu kita sebagai developer yang biasa membuat aplikasi, lalu test dan deliver ke pengguna. Apa ada pembaca disini yang membuat aplikasi lalu langsung di deliver ke pengguna? ๐Ÿ˜€ Jangan ya..

Di proses test yang biasanya kita lakukan secara manual, sekarang kita ubah proses itu dengan cara membuat unit testing / kode kecil untuk melakukan testing. Kode-kode tersebut akan dijalankan sebelum proses deploy (aplikasi siap digunakan oleh user). Jadi sebelum user menggunakan aplikasi yang kita buat, kita (programmer) pantau terlebih dahulu proses testing ini.

Bagaimana jika flow aplikasi berubah?

Jika aplikasi berubah, kita harus ubah juga kode unit testing sesuai dengan perubahan tersebut. Ini tentunya menambah pekerjaan lagi buat programmer ya. Tapi langkah ini diambil untuk memastikan produk kita sesuai dengan rencana dan meminimalisir error yang muncul.

Kenapa menggunakan SQLite untuk Unit Testing?

Sebelum membahas kenapa menggunakan SQLite, pastikan kita sudah mengetahui apa itu SQLite dan manfaat SQLite dalam database di aplikasi kita. SQLite biasanya digunakan untuk menyimpan data secara local di lingkungan aplikasi dengan beberapa keterbatasan dan kelebihan yang ada pada SQLite. Saya tidak akan membahas jauh tentang SQLite ya.

Alasan kenapa menggunakan SQLite untuk unit testing adalah untuk memudahkan kita melakukan testing yang berkaitan dengan database. Ketika kita programmer sudah bekerja bersama programmer lain, kita kadang tidak mungkin memiliki database dengan data yang sama.

Ada beberapa programmer yang sedang mengerjakan feature tertentu dengan data di database yang mungkin berbeda. Namun perlu diingat, pada suatu waktu pasti feature dari tiap programmer akan disatukan dalam satu kode yang sama sehingga tiap-tiap programmer harus memiliki data yang sama persis. Ketika itu terjadi, tentu saja proses testing juga tetap harus berjalan.

Lalu kenapa tidak menggunakan MySQL? Atau RDBMS Lainnya?

Awalnya saya juga menggunakan MySQL karena aplikasi yang sedang berjalan juga menggunakan MySQL. Namun setelah mencari informasi jika kita menggunakan SQLite itu lebih kecil dan lebih cepat melakukan testing dengan data yang sama. Kadang beberapa unit kecil testing flow yang kita tulis hanya beberapa proses kecil yang juga bisa dilakukan dengan SQLite.

Dengan keterbatasan SQLite, temen-temen juga perlu memperhatikan itu ketika melakukan testing jangan sampai ada query atau data atau mungkin join table yang terbatasi oleh SQLite. Beberapa batasan dari SQLite ini bisa temen-temen lihat di link berikut ini: https://www.sqlite.org/limits.html

Apa maksud SQLite dengan Memory?

Kalau temen-temen pernah menggunakan SQlite, kita tentu store atau simpan data di salah satu file di hardisk atau storage. Nah SQLite ini juga bisa menyimpan tanpa file di storage, kita bisa simpan itu di memory. Mungkin ada yang kepikiran seperti redis ya, eh wait, ada yang belum tahu apa itu redis? Silahkan baca tulisan sederhana saya tentang redis di sini: https://www.adiputra.web.id/mengenal-redis-teori-sederhana/

Temen-temen bisa baca dokumentasi SQLite dengan Memory di link berikut ini: https://www.sqlite.org/inmemorydb.html

Dengan adanya memory ini, tentu akan lebih mempercepat proses testing karena unit testing yang kita lakukan mengolah data dimana data itu berada di memory.

Setting SQLite Memory pada Unit Testing Laravel

Setelah kita ketahui beberapa info diatas, yuk sekarang kita coba untuk membuat unit testing sederhana di Laravel. Saya ingin mencoba membuat unit testing untuk CRUD Kupon. Sebelumnya pastikan juga temen-temen sudah menginstall SQLite ya. Yang diperlukan pada proses ini adalah:

  1. Membuat migration table
  2. Membuat factory dengan Faker
  3. Melakukan Setting pada PHPUnit
  4. Membuat unit testing
  5. Jalankan testing

Membuat Migration Table

Sebelum memulai, tentunya pasti kita install laravel di komputer kita ya, jangan komputer orang lain apalagi komputer mantan #eh #becanda

Buat migration table, saya ingin membuat migration table kupon seperti berikut ini:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateCouponsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('coupon', function (Blueprint $table) {
            $table->increments('coupon_id');
            $table->unsignedInteger('shop_id')->index();
            $table->string('coupon_name',45)->index();
            $table->tinyInteger('coupon_type');
            $table->string('coupon_code',45)->index();
            $table->decimal('coupon_amount');
            $table->decimal('coupon_minpurchase');
            $table->date('coupon_expire')->nullable();
            $table->date('coupon_start')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('coupon');
    }
}

Ada yang bertanya mungkin kenapa kita ingin membuat unit testing tapi harus membuat migration dulu?

Kalau temen-temen ingin membuat unit testing tanpa data yaa tidak perlu ada migration. Migration disini berguna untuk menyetting struktur table di SQLite yang kita buat nanti. Jadi tanpa migration, unit testing kita akan selalu gagal karena struktur table tidak ditemukan ๐Ÿ™‚

Membuat Factory dengan Faker

Ada yang belum mengerti Factory di Laravel? mudah-mudahan sudah mengerti ya. Gunanya membuat Factory di Laravel adalah agar kita bisa meng-generate data dengan mudah di struktur table yang sebelumnya kita buat dengan migration. Factory ini akan kita panggil nanti di unit testing yang akan kita buat. Jika ada temen-temen yang ingin membaca apa itu factory mungkin bisa baca dokumentasi pada Laravel disini: https://laravel.com/docs/5.8/database-testing#generating-factories

Faker sendiri adalah salah satu package yang memudahkan kita mengisi data yang mirip dengan keadaan kolom. Karena kita diatas membahas factory dimana data akan di generate. Tentu saja generate tersebut tidak secara otomatis. Kita harus isi data tersebut lalu proses pengisian biasanya kita lakukan dengan memanggil method random yang ada pada PHP ataupun Helper di Laravel. Nah dengan Faker, kita bisa mengisi data yang mirip dengan kondisi dari kolom data. Misalnya data kolom nomor telp secara random, data kolom nama secara random, data kolom email secara random.

Temen-temen yang ingin lihat package Faker ini bisa lihat disini tanpa harus gugel: https://github.com/fzaninotto/Faker

Lalu berikut adalah factory yang saya buat dengan nama CouponFactory.php:

<?php

use Faker\Generator as Faker;

$factory->define(App\Models\Coupon::class, function (Faker $faker) {

    $coupon_ongoing = $faker->randomElement(array(0,1));
    $coupon_start = null;
    $coupon_expire = null;

    if($coupon_ongoing == 0) {
        $start = $faker->dateTimeBetween('+1 days', '+90 days');

        $coupon_start = $start->format('Y-m-d');
        $coupon_expire = $faker->dateTimeBetween($start, $start->format('Y-m-d H:i:s') . '+60 days')->format('Y-m-d');
    }

    return [
        'coupon_id' => $faker->randomDigit,
        'shop_id' => $faker->randomDigit,
        'coupon_name' => $faker->name,
        'coupon_code' => $faker->name,
        'coupon_type' => $faker->randomElement(array(1,2)),
        'coupon_amount' => $faker->randomDigit,
        'coupon_quantity' => $faker->randomDigit,
        'coupon_minpurchase' => $faker->randomDigit,
        'coupon_start' => $coupon_start,
        'coupon_expire' => $coupon_expire
    ];
});

Melakukan Setting pada PHPUnit

Setelah kita membuat Factory, sekarang kita setting di PHPUnit. PHPUnit adalah package yang memudahkan kita melakukan proses testing. Ada beberapa method bawaan dari PHPUnit yang memudahkan kita untuk mengecek data, atau kondis dari flow aplikasi yang kita buat.

Laravel sendiri sudah secara default mengikutsertakan PHPUnit di framework ini. Dalam artian, temen-temen tidak perlu menginstall package atau data ketika menggunakan PHPUnit ini. Yang perlu temen-temen lakukan adalah tambahkan data pada file phpunit.xml dengan tag default seperti ini (pada laravel 5.7)

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>

        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="BCRYPT_ROUNDS" value="4"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
        <env name="MAIL_DRIVER" value="array"/>
    </php>
</phpunit>

Menjadi seperti ini:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>

        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
        <env name="MAIL_DRIVER" value="array"/>
        <env name="DB_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
    </php>
</phpunit>

Kalau temen-temen lihat, yang utama adalah penambahan key name DB_CONNECTION = sqlite dan DB_DATABASE = :memory:

Kedua tambahan key name tersebut untuk menyetting database connection dan db name kita saat melakukan testing.

Laravel otomatis akan membaca data yang ada pada phpunit.xml ketika kita melakukan test. Temen-temen juga bisa menggunakan file .env.testing jika mau dan perlu beberapa setting ya.

Membuat Unit Testing

Setelah itu, sekarang kita coba membuat unit testing coupon untuk mendapatkan list seperti kode berikut ini:

<?php
namespace Tests\Feature\V3;

use App\Models\Shop;
use App\Models\User;
use App\Models\Coupon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use Laravel\Passport\Passport;

class CouponTest extends TestCase
{
    use RefreshDatabase;

    public function setUp()
    {
        parent::setUp();
    }

    /**
     * @test
     */
    public function user_want_get_resource_coupon_list()
    {
        Passport::actingAs(
            factory(User::class)->create(),
            ['all'],
            'api'
        );
        
        $this->shop = factory(Shop::class)->create();

        factory(Coupon::class)->create([
            'shop_id' => $this->shop->shop_id
        ]);

        $response = $this->get('/api/shop/' . $this->shop->shop_id . '/coupon');

        $response
            ->assertSuccessful()
            ->assertSee('shop_id')
            ->assertSee('coupon_name')
            ->assertSee('coupon_code');
    }

}

Jika temen-temen lihat diatas, saya memanggil factory Coupon::class yang telah kita buat sebelumnya. Lalu ada beberapa factory lain seperti factory User dan Shop yang tidak saya bahas.

Intinya dari kode unit testing diatas adalah, kita membuat data dengan factory coupon, setelah data dibuat maka tentu akan masuk ke SQLite Memory. Dan setelah itu kita coba panggil API Restful untuk menghandle data list coupon yang kita baru saja buat.

Kode Controller dan Route untuk menghandle data API Restful tidak saya tulis disini. Saya hanya menjelaskan flow ketika kita menggunakan SQLite Memory pada Unit testing yang kita buat.

Sampai disini, kritik saran atau ada yang tidak sesuai, boleh komentar di kolom komentar ya. Saya juga masih belajar ๐Ÿ™‚

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: