Nafies Luthfi

Life will always feel wonderful if we always think positively.

Testing Laravel: Refactor Controller dengan Model Behaviour

Bismillahirrahmanirrahim.

Pada artikel sebelumnya kita sudah membahas tentang toggle status task, kali ini kita melakukan refactor pada method TasksController@toggle untuk fitur tersebut. Sekarang coba teman-teman lihat potongan script berikut :

<?php

// TasksController

    // ... method destroy

    public function toggle(Task $task)
    {
        if ($task->is_done == 1) {
            $task->is_done = 0;
        } else {
            $task->is_done = 1;
        }

        $task->save();

        return back();
    }

Kemudian kita bandingkan dengan ini.

<?php

// TasksController

    // ... method destroy

    public function toggle(Task $task)
    {
        $task->toggleStatus();

        return back();
    }

Kira-kira lebih “readable” mana? Pasti potongan script yang kedua, kan ya? Dengan script yang kedua, ibarat :

  • TasksController adalah seorang manager.
  • Task model adalah staff (dari manager) untuk mengurus record-record tabel task.
  • Method toggleStatus() adalah jobdesk untuk staff agar mengubah nilai is_done.
  • Manager memberi perintah kepada staffnya dengan memanggil method toggleStatus().
  • Staff melakukan tugasnya sesuai perintah dan jobdesk yang sudah didefinisikan.
  • Setelah staff selesai bekerja, manager kembali melanjutkan tugasnya, yaitu return back();

Insyaallah mudah dipahami ya? Inilah yang saya pahami dari cara kerja Object Oriented Programming, di mana ada interaksi antar object (class), dan setiap object akan bekerja sesuai dengan tugas dan tujuannya.

Kita mulai

Baik, seperti biasa, sebelum mulai edit sana-sini, kita jalankan PHPUnit dulu, sekedar memastikan kita dapat hijau.

# 1
$ vendor/bin/phpunit

Seharusnya kita tetap dapat OK (8 tests, 47 assertions).

Baik, kita lanjutkan dengan membuka file TasksController, kemudian kita edit method toggle sesuai dengan keperluan kita.

<?php

// TasksController

    // ... method destroy

    public function toggle(Task $task)
    {
        // if ($task->is_done == 1) {
        //     $task->is_done = 0;
        // } else {
        //     $task->is_done = 1;
        // }

        // $task->save();

        $task->toggleStatus();

        return back();
    }

Simpan, dan jalankan PHPUnit.

# 2
$ vendor/bin/phpunit
Hasil: Error
A request to [http://localhost/tasks/1/toggle] failed. Received status code [500]
...
Caused by
BadMethodCallException: Call to undefined method App\Task::toggleStatus() ...
Penyebab

Kita belum memiliki method toggleStatus() pada model App\Task.

Solusi

Kita buat method toggleStatus() pada model App\Task.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    protected $fillable = ['name', 'description'];

    public function toggleStatus()
    {
        //
    }
}

Simpan, jalankan PHPUnit

# 3
$ vendor/bin/phpunit
Hasil: Error
Unable to find row in database table [tasks] that matched attributes
[{"id":1,"is_done":0}].
Failed asserting that 0 is greater than 0.
Penyebab

Method toggleStatus() pada model App\Task belum melakukan apa-apa.

Solusi

Kita definisikan script untuk toggle status is_done pada method toggleStatus() di model App\Task. Di sini serupa dengan kita mendifinisikan “jobdesk” atau tugas agar model App\Task mengubah status is_done.

Membuat unit test untuk model ‘App\Task’

Untuk mendefinisikan tugas model ini, mari kita buat Unit Test untuk model App\Task, untuk memastikan method ini berjalan sesuai dengan keinginan kita (yaitu mengubah nilai is_done).

$ php artisan make:test --unit Models/TaskTest

Laravel akan membuat test class baru tests/Unit/Models/TaskTest.php. Sekarang kita edit file tersebut.

<?php

namespace Tests\Unit\Models;

use App\Task;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class TaskTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function task_model_has_toggle_status_method()
    {
        // Generate 1 record task pada table `tasks`.
        $task = factory(Task::class)->create();

        // Panggil method `toggleStatus()` pada model `App\Task`
        $task->toggleStatus();

        // Kolom is_done pada record task berubah menjadi 1
        $this->seeInDatabase('tasks', [
            'id'      => $task->id,
            'is_done' => 1,
        ]);

        // Panggil method `toggleStatus()` pada model `App\Task` (lagi)
        $task->toggleStatus();

        // Kolom is_done pada record task berubah menjadi 0
        $this->seeInDatabase('tasks', [
            'id'      => $task->id,
            'is_done' => 0,
        ]);
    }
}

Secara umum, struktur method test ini mirip seperti Feature Test untuk toggle status pada artikel sebelumnya, tetapi disini tidak ada submit form, kita hanya menguji method toggleStatus() dari model App\Task saja, maka dari itu testing ini kita golongkan sebagai Unit Test.

Baik, pastikan simpan file ini dan jalankan PHPUnit. Tetapi kali ini kita hanya menjalankan PHPUnit untuk satu class saja, yaitu TaskTest, sehingga kita tambahkan parameter --filter TaskTest pada perintah PHPUnit.

# 4
$ vendor/bin/phpunit --filter TaskTest
Hasil: Error
Unable to find row in database table [tasks] that matched attributes
[{"id":1,"is_done":0}].
Failed asserting that 0 is greater than 0.
Penyebab

Method toggleStatus() pada model App\Task belum melakukan apa-apa (ini mirip seperti hasil error sebelumnya ya).

Solusi

Kita definisikan script untuk toggle status is_done pada method toggleStatus() di model App\Task. Script nya bisa kita tiru dari script di method TasksController@toggle, tetapi variabel $task kita ganti dengan $this, karena saat ini kita sedang meng-edit class App\Task itu sendiri.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    protected $fillable = ['name', 'description'];

    public function toggleStatus()
    {
        if ($this->is_done == 1) {
            $this->is_done = 0;
        } else {
            $this->is_done = 1;
        }

        $this->save();

        return $this;
    }
}

Baik kita jalankan lagi PHPUnit dengan --filter TaskTest.

# 5
$ vendor/bin/phpunit --filter TaskTest

Hasilnya :

toggleStatus method test passed{:id=“toggleStatus-method-test-passed”}

Sip kita sudah dapat hijau untuk testing model TaskTest.

Sekarang kita jalankan PHPUnit untuk keseluruhan testingnya.

# 6
$ vendor/bin/phpunit

Hasilnya :

All tests passed

OK (9 tests, 49 assertions). Yap, secara otomatis semua testing script passed. Mengapa? Karena testing yang failed (error) pada tahap #3 ini, disebabkan oleh “perkara yang sama” dengan testing yang failed pada tahap #4, yaitu karena kita belum mendefinisikan tugas dari method toggleStatus().

Ketika method toggleStatus() sudah dibuat sesuai tugasnya (mengubah nilai is_done), dan testing tahap #5 passed, maka tahap #3 ini juga akan passed.

Dengan demikian refactor yang kita lakukan untuk method TasksController@toggle ini berjalan sukses.

Kesimpulan

Baik, sampai disini kita simpulkan manfaat dari refactor controller ke sebuah method pada model, karena kemungkinan diantara teman-teman ada yang bertanya :

“Mengapa repot-repot merefactor method TasksController@toggle ini? Kan method ini tidak terlalu sulit dipahami, ya? Lalu dibuatkan unit testing pula, kenapa?”.

Jawabannya kita buat kesimpulan :

  1. Method dari controller menjadi lebih ramping.
  2. Script pada controller menjadi “readable” (lebih mudah dipahami).
  3. Controller (sebagai manager) tinggal memerintahkan model (sebagai staffnya) sesuai dengan “jobdesk”-nya.
  4. Method toggleStatus() pada model App\Task adalah model behaviour atau perilaku model.
  5. Unit testing untuk model memastikan bahwa model behaviour bekerja sesuai spesifikasi, atau bisa dikatakan, model berperilaku sebagaimana yang kita inginkan.

Bisa dibayangkan jika pada project-project aplikasi yang kita buat, kita dapat me-refactor method-method pada controller ke dalam method-method model, agar method-method pada controller kita tidak “gendut”.

Hal ini juga akan membuat source-code kita menjadi lebih “readable”, baik untuk orang lain yang melihat/me-review source-code yang kita kerjakan, maupun untuk diri kita sendiri (jika melihat source-code ini beberapa bulan setelah project selesai).

Baik teman-teman, demikian pembahasan kita tentang “refactor controller dengan model behaviour”. Mudah-mudahan pembahasan pada artikel ini dapat menjadi inspirasi bagi teman-teman dalam melakukan refactor controller.

Semua perubahan script project Testing-TDD pada artikel ini bisa dilihat pada commit ini (dengan sedikit clean-up).

Terima kasih teman-teman atas waktunya.