Nafies Luthfi

Life will always feel wonderful if we always think positively.

Testing Laravel: Toggle Status Task dengan Form Button

Bismillahirrahmanirrahim.

Kalau teman-teman ingat, pada artikel saat mendapatkan passed test, kita membuat file migration untuk tabel tasks dengan kolom is_done untuk menyatakan status task yang bersangkutan. Dari kemarin-kemarin kita belum melakukan apa-apa terhadap kolom tersebut karena secara default dia bernilai 0. Nah sekarang kita akan mencoba memanfaatkan kolom tersebut untuk mengubah status task.

Jika teman-teman sudah mengikuti artikel Testing Laravel ini dari awal, silakan menggunakan projectnya. Saya akan mulai dari commit ini pada repo Laravel-TDD ini.

Seperti biasa, sebelum mulai kita jalankan testing dulu untuk memastikan project kita dalam kondisi “sehat”.

# 1
$ vendor/bin/phpunit

Seharusnya kita tetap dapat hasil OK (7 tests, 36 assertions).

Membuat Failing Test

Baik, kita lanjutkan dengan membuat test method untuk fitur toggle status task ini. Pertama kita buka file tests/Feature/ManageTasksTest.php, kemudian kita tambahkan satu test method baru user_can_toggle_task_status.

<?php

// ...

class ManageTasksTest extends TestCase
{
    // ... user_can_create_a_task()

    // ... user_can_browse_tasks_index_page()

    // ... user_can_edit_an_existing_task()

    // ... user_can_delete_an_existing_task()

    /** @test */
    public function user_can_toggle_task_status()
    {
        //
    }
}

Sekarang kita buat PHP Comment line untuk membantu kita membuat testing script.

<?php

    /** @test */
    public function user_can_toggle_task_status()
    {
        // Generate 1 record task pada table `tasks`.

        // User membuka halaman Daftar Task.

        // User tekan tombol dengan id="toggle_task_1"

        // Lihat halaman web ter-redirect ke halaman daftar task

        // Kolom is_done pada record task berubah menjadi 1

        // User tekan tombol dengan id="toggle_task_1" (lagi)
        // untuk mengembalikan status task

        // Lihat halaman web ter-redirect ke halaman daftar task

        // Kolom is_done pada record task berubah menjadi 0
    }

Sekarang kita buat testing script-nya.

<?php

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

        // User membuka halaman Daftar Task.
        $this->visit('/tasks');

        // User tekan tombol dengan id="toggle_task_1"
        // Dimana angka 1 adalah id dari $task
        $this->press('toggle_task_'.$task->id);

        // Lihat halaman web ter-redirect ke halaman daftar task
        $this->seePageIs('/tasks');

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

        // User tekan tombol dengan id="toggle_task_1" (lagi)
        // untuk mengembalikan status task
        $this->press('toggle_task_'.$task->id);

        // Lihat halaman web ter-redirect ke halaman daftar task
        $this->seePageIs('/tasks');

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

Cukup panjang ya, karena di sini kita menguji “toggle”, ketika tombol yang sama ditekan, maka statusnya akan kembali ke status sebelumnya.

Mendapatkan Passed Test

Baik, simpan file, kita mulai jalankan PHPUnit.

# 2
$ vendor/bin/phpunit
Hasil: Error
InvalidArgumentException: Could not find a form that has
submit button [toggle_task_1].
Penyebab

Kita belum memiliki form yang memiliki tombol toggle_task_1. Di mana toggle_task_1 bisa berupa tulisan atau atribut value pada tombol submit, bisa juga berupa atribut id dari tombol submit tersebut.

Solusi (Membuat Form Button)

Pada view tasks.index bagian daftar Task (di bawah form delete task) kita buat form dengan tombol toggle_task_1.

<!-- resources/views/tasks/index.blade.php -->

<!-- @foreach ($tasks as $task) -->
<li class="list-group-item">
    <form action="{% raw" >}}{{ url('tasks/'.$task->id) }}{% endraw" >}}" method="post" class="pull-right"
        onsubmit="return confirm('Are U sure to delete this task?')">
        {% raw" >}}{{ csrf_field() }}{% endraw" >}}
        {% raw" >}}{{ method_field('delete') }}{% endraw" >}}
        <input type="submit" value="X" id="delete_task_{% raw" >}}{{ $task->id }}{% endraw" >}}" class="btn btn-link btn-xs">
        <a
            href="{% raw" >}}{{ url('tasks') }}{% endraw" >}}?action=edit&id={% raw" >}}{{ $task->id }}{% endraw" >}}"
            id="edit_task_{% raw" >}}{{ $task->id }}{% endraw" >}}">
            edit
        </a>
    </form>
    <form action="{% raw" >}}{{ url('tasks/'.$task->id.'/toggle') }}{% endraw" >}}" method="post">
        {% raw" >}}{{ csrf_field() }}{% endraw" >}}
        {% raw" >}}{{ method_field('patch') }}{% endraw" >}}
        <input type="submit" value="Toggle" id="toggle_task_{% raw" >}}{{ $task->id }}{% endraw" >}}">
    </form>
    {% raw" >}}{{ $task->name }}{% endraw" >}} <br>
    {% raw" >}}{{ $task->description }}{% endraw" >}}
</li>
<!-- @endforeach -->

Di sini kita membuat sebuah form dengan action 'tasks/1/toggle' (angka 1 adalah id dari task yang bersangkutan), yang di dalamnya langsung kita berikan csrf_field() dan method_field('patch'), kemudian ada tombol submit dengan id="toggle_task_1".

Simpan, kemudian jalankan PHPUnit.

# 3
$ vendor/bin/phpunit
Hasil: Error
A request to [http://localhost/tasks/1/toggle] failed. Received status code [404]
...
Caused by
Symfony\Component\HttpKernel\Exception\NotFoundHttpException
Penyebab

Request yang dikirimkan ke tasks/1/toggle, belum route yang melayaninya.

Solusi

Kita tambahkan route untuk melayani requestnya di file routes/web.php.

<?php
// routes/web.php

// ..

Route::get('/tasks', 'TasksController@index');
Route::post('/tasks', 'TasksController@store');
Route::patch('/tasks/{task}', 'TasksController@update');
Route::delete('/tasks/{task}', 'TasksController@destroy');
Route::patch('/tasks/{task}/toggle', 'TasksController@toggle');

// ..

Jalankan PHPUnit

# 4
$ vendor/bin/phpunit
Hasil: Error
A request to [http://localhost/tasks/1/toggle] failed. Received status code [500]
...
Caused by
BadMethodCallException: Method [toggle] does not exist.
Penyebab

Yap, sudah diduga, belum ada method toggle di TasksController kita.

Solusi

Kita buat method TasksController@toggle untuk melayani request ini. Method tersebut langsung kita isi dengan script untuk mengupdate kolom is_done dan redirect kembali ke halaman sebelumnya.

<?php

// TasksController

    // ... method destroy

    public function toggle(Task $task)
    {
        $task->is_done = 1;
        $task->save();

        return back();
    }

Jalankan PHPUnit

# 5
$ 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

Pada method TasksController@toggle kita hanya mengeset $task->is_done = 1 padahal seharusnya (yang kita inginkan) : jika kolom is_done bernilai 0 maka jadikan 1. Sebaliknya, jika saat ini kolom is_done bernilai 1 maka jadikan 0.

Solusi

Kita buat method TasksController@toggle untuk melakukan toggle kolom is_done dengan nilai 1 dan 0.

<?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();
    }

Jalankan PHPUnit

# 6
$ vendor/bin/phpunit

Passed Toggle Task Status

Alhamdulillah, sekarang kita sudah dapat hijau. Sekarang kita melihat tampilan melalui browser untuk melihat hasilnya.

Tampilan Toggle Task Status

Hmmm, kurang bagus ya. Sebenarnya fitur tombol “toggle”-nya sudah bekerja, hanya saya tidak ada perubahan tampilan yang bisa kita lihat di browser. Tidak apa-apa sekarang kita perbaiki tampilannya.

Mempercantik Tampilan

Kita buka view tasks.index, kemudian kita ubah menjadi seperti ini :

<!-- resources/views/tasks/index.blade.php -->

<!-- @foreach ($tasks as $task) -->
<li class="list-group-item {% raw" >}}{{ $task->is_done ? 'task-done' : '' }}{% endraw" >}}">
    <form action="{% raw" >}}{{ url('tasks/'.$task->id) }}{% endraw" >}}" method="post" class="pull-right"
        onsubmit="return confirm('Are U sure to delete this task?')">
        {% raw" >}}{{ csrf_field() }}{% endraw" >}}
        {% raw" >}}{{ method_field('delete') }}{% endraw" >}}
        <input type="submit" value="X" id="delete_task_{% raw" >}}{{ $task->id }}{% endraw" >}}" class="btn btn-link btn-xs">
        <a
            href="{% raw" >}}{{ url('tasks') }}{% endraw" >}}?action=edit&id={% raw" >}}{{ $task->id }}{% endraw" >}}"
            id="edit_task_{% raw" >}}{{ $task->id }}{% endraw" >}}">
            edit
        </a>
    </form>
    <form action="{% raw" >}}{{ url('tasks/'.$task->id.'/toggle') }}{% endraw" >}}" method="post">
        {% raw" >}}{{ csrf_field() }}{% endraw" >}}
        {% raw" >}}{{ method_field('patch') }}{% endraw" >}}
        <input
            type="submit" value="{% raw" >}}{{ $task->name }}{% endraw" >}}"
            id="toggle_task_{% raw" >}}{{ $task->id }}{% endraw" >}}"
            class="btn btn-link no-padding">
    </form>
    {% raw" >}}{{ $task->description }}{% endraw" >}}
</li>
<!-- @endforeach -->

Kalau teman-teman perhatikan. bagian yang diubah adalah :

  1. <li class="list-group-item {% raw" >}}{{ $task->is_done ? 'task-done' : '' }}{% endraw" >}}">. Jika is_done bernilai true atau 1, maka ditambahkan class “task-done” pada tag <li> tersebut.
  2. tombol submit Toggle menjadi nama task masing-masing. Sehingga nama masing-masing task akan berperan sebagai toggle status task tersebut.
  3. tombol submit Toggle ditambahkan class=“btn btn-link no-padding”.

Di sini ada 2 class css yang kita buat, yaitu “task-done” pada <li> dan “no-padding” pada tombol submit Toggle. Sekarang kita buat style untuk kedua class tambahan ini.

Buka view layouts.app, tambahkan tag <style> pada bagian <head>.

<!-- resources/views/layouts/app.blade.php -->

<head>
    <!-- ... -->

    <!-- Styles -->
    <link href="{% raw" >}}{{ asset('css/app.css') }}{% endraw" >}}" rel="stylesheet">
    <style>
        .task-done {
            background-color: #dff0d8;
            text-decoration: line-through;
            color: #3c763d;
        }
        .btn.no-padding {
            padding: 0;
        }
    </style>
</head>

Nah, sekarang simpan dan kita lagi di browser, hasilnya kurang lebih begini :

Tampilan Toggle Task Status

Oke, sudah lumayan bagus ya. Jadi ketika kita klik nama task, maka akan menjadi “toggle” status task. Background hijau dan tulisan strikethrough untuk task dengan is_done = 1.

Terakhir, kita jalankan PHPUnit untuk memastikan perubahan di view tidak merusak fitur toggle status task kita.

# 7
$ vendor/bin/phpunit

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

Baik teman-teman sampai disini pembahasan kita tentang form button untuk toggle status task. Semua perubahan script pada artikel ini bisa teman-teman lihat pada commit ini.

Insyaallah pada artikel berikutnya kita akan bahas tentang refactor di controller dengan model behaviour (perilaku kelas model) khusus untuk method TasksController@toggle.

Terima kasih atas waktunya.