Nafies Luthfi

Life will always feel wonderful if we always think positively.

Testing Laravel: Form Edit Task

Bismillahirrahmanirrahim.

Pada artikel sebelumnya kita sudah membuat link untuk melakukan edit task, tetapi link tersebut belum bekerja. Link tersebut akan kita gunakan untuk menampilkan form edit task pada halaman yang sama (view tasks.index).

Jika teman-teman sudah mengikuti artikel Testing Laravel ini sejak awal, silakan menggunakan projectnya. Jika tidak, silakan menggunakan commit pada repo ini (sampai update artikel sebelumnya).

Baik, seperti biasa, kita mulai kerja dengan menjalankan testing dulu.

# 1
$ vendor/bin/phpunit

OK (7 tests, 22 assertions)

Untuk membuat form edit task, kita lakukan dengan meng-edit file tests/Feature/ManageTasksTest.php. Pada file ini kita perhatikan method user_can_edit_an_existing_task. Kita akan bekerja dari sini.

<?php

// ...

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

    /** @test */
    public function user_can_edit_an_existing_task()
    {
        $this->assertTrue(true);
    }
    
    // ... user_can_delete_an_existing_task()
}

Baik, kita buat PHP comment line yang menjadi panduan kita.

<?php

    /** @test */
    public function user_can_edit_an_existing_task()
    {
        // Generate 1 record task pada table `tasks`.
        
        // User membuka halaman Daftar Task.
        
        // Klik tombol edit task
        
        // Lihat URL yang dituju sesuai dengan target
        
        // Tampil form Edit Task

        // User submit form berisi nama dan deskripsi task yang baru

        // Lihat halaman web ter-redirect ke URL sesuai dengan target
        
        // Record pada database berubah sesuai dengan nama dan deskripsi baru
    }

Oke sekarang kita buat script testing sesuai dengan langkah kerja di atas.

<?php

    /** @test */
    public function user_can_edit_an_existing_task()
    {
        // Generate 1 record task pada table `tasks`.
        $task = factory(Task::class)->create();
        
        // User membuka halaman Daftar Task.
        $this->visit('/tasks');
        
        // Klik tombol edit task (link dengan id="edit_task_1")
        // Dimana angka 1 adalah id dari $task
        $this->click('edit_task_'.$task->id);
        
        // Lihat URL yang dituju sesuai dengan target
        $this->seePageIs('/tasks?action=edit&id='.$task->id);

        // Tampil form Edit Task, kita cek apakah ada form dengan
        // id="edit_task_1" dan action="tasks/1"
        $this->seeElement('form', [
            'id'     => 'edit_task_'.$task->id,
            'action' => url('tasks/'.$task->id),
        ]);

        // User submit form berisi nama dan deskripsi task yang baru
        $this->submitForm('Update Task', [
            'name' => 'Updated Task',
            'description' => 'Updated task description.',
        ]);

        // Lihat halaman web ter-redirect ke URL sesuai dengan target
        // yaitu '/tasks', kembali ke daftar task
        $this->seePageIs('/tasks');
        
        // Record pada database berubah sesuai nama dan deskripsi baru
        $this->seeInDatabase('tasks', [
            'id' => $task->id,
            'name' => 'Updated Task',
            'description' => 'Updated task description.',
        ]);
    }

Simpan, kemudian jalankan PHPUnit.

# 2
$ vendor/bin/phpunit
Hasil: Error
Failed asserting that the page contains the element [form] with the attributes
{"id":"edit_task_1","action":"http:\/\/localhost\/tasks\/1"}.
Please check the content above.
Penyebab

Kita belum memiliki <form id="edit_task_1" action="http://localhost/tasks/1" method="post"></form>.

Solusi

Pada view tasks.index kita buat <form id="edit_task_1" action="http://localhost/tasks/1" method="post"></form>.

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

    <div class="col-md-4">
        <form id="edit_task_1" action="{% raw" >}}{{ url('tasks/1') }}{% endraw" >}}" method="post"></form>

        <h2>New Task</h2>
        <!-- form create task -->
    </div>
# 3
$ vendor/bin/phpunit
Hasil: Error
InvalidArgumentException: Could not find a form that has submit button
[Update Task].

Stop!!

Kalau kita perhatikan, di sini progress kita berlanjut, karena error yang kita dapatkan sudah berubah (artinya error sebelumnya sudah solved). Sebenarnya belum, karena pada script testing di bawah ini kita hanya menguji apakah form dengan kriteria :
id="edit_task_1" action="{% raw" >}}{{ url('tasks/1') }}{% endraw" >}}" tampil di halaman web.

<?php

    public function user_can_edit_an_existing_task()
    {
        // ..
        
        // Tampil form Edit Task, kita cek apakah ada form dengan
        // id="edit_task_1" dan action="tasks/1"
        $this->seeElement('form', [
            'id'     => 'edit_task_'.$task->id,
            'action' => url('tasks/'.$task->id),
        ]);
        
        // ..
    }

Padahal kita masih menggunakan sintaks html statis <form id="edit_task_1" action="{% raw" >}}{{ url('tasks/1') }}{% endraw" >}}" method="post"></form>. Artinya kita harus membuat angka 1 disini menjadi dinamis sesuai dengan id dari task yang akan kita edit.

Untuk membuat angka 1 tersebut menjadi dinamis, kita kerjakan mulai dari controllernya. Buka TasksController, kemudian buat blok if untuk mengecek apakah terdapat query string id dan action=edit pada request. Ini sesuai dengan link edit task yang kita buat pada view tasks.index pada artikel sebelumnya.

<?php

// TasksController
    
    public function index()
    {
        $tasks = Task::all();
        $editableTask = null;

        if (request('id') && request('action') == 'edit') {
            $editableTask = Task::find(request('id'));
        }

        return view('tasks.index', compact('tasks', 'editableTask'));
    }

    // ... method store

Penambahan script di atas dimaksudkan agar ketika ada query string id dan action=edit, kita buat variabel $editableTask berisi record model Task yang ingin kita edit.

Oke, selanjutnya kita edit view tasks.index dengan menambahkan blok if untuk mengecek apakah variabel $editabelTask tidak bernilai null dan terdapat action=edit pada request, serta menyesuaikan angka id form edit task.

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

    <div class="col-md-4">
        @if (! is_null($editableTask) && request('action') == 'edit')
        <form id="edit_task_{% raw" >}}{{ $editableTask->id }}{% endraw" >}}" action="{% raw" >}}{{ url('tasks/'.$editableTask->id) }}{% endraw" >}}" method="post"></form>
        @endif

        <h2>New Task</h2>
        <!-- form create task -->
    </div>

Baik sekarang jalankan PHPUnit.

# 4
$ vendor/bin/phpunit
Hasil: Error
InvalidArgumentException: Could not find a form that has submit button
[Update Task].

Oke, hasil error ini tetap sama dengan hasil error pada tahap sebelumnya, sebelum kita membuat variabel $editableTask di controller dan view barusan.

Baik kita lanjutkan untuk memperbaiki error ini.

Penyebab

Kita belum memiliki tombol submit Update Task pada form edit task.

Solusi

Kita buat tombol submit Update Task pada form edit task.

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

    <div class="col-md-4">
        @if (! is_null($editableTask) && request('action') == 'edit')
        <form id="edit_task_{% raw" >}}{{ $editableTask->id }}{% endraw" >}}" action="{% raw" >}}{{ url('tasks/'.$editableTask->id) }}{% endraw" >}}" method="post">
            <input type="submit" value="Update Task" class="btn btn-primary">
        </form>
        @endif

        <h2>New Task</h2>
        <!-- form create task -->
    </div>

Jalankan PHPUnit

# 5
$ vendor/bin/phpunit
Hasil: Error
InvalidArgumentException: Unreachable field "name"
Penyebab

Kita belum input text name.

Solusi

Kita buat input text name pada edit task. Error yang serupa juga akan muncul untuk input description, maka kita buat sekalian saja. Kita jua membuat {% raw" >}}{{ csrf_field() }}{% endraw" >}} dan {% raw" >}}{{ method_field('patch') }}{% endraw" >}} karena proses ini adalah proses update record.

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

    <div class="col-md-4">
        @if (! is_null($editableTask) && request('action') == 'edit')
        <form id="edit_task_{% raw" >}}{{ $editableTask->id }}{% endraw" >}}" action="{% raw" >}}{{ url('tasks/'.$editableTask->id) }}{% endraw" >}}" method="post">
            {% raw" >}}{{ csrf_field() }}{% endraw" >}}
            {% raw" >}}{{ method_field('patch') }}{% endraw" >}}
            <div class="form-group">
                <label for="name" class="control-label">Name</label>
                <input id="name" name="name" class="form-control" type="text">
            </div>
            <div class="form-group">
                <label for="description" class="control-label">Description</label>
                <textarea id="description" name="description" class="form-control"></textarea>
            </div>
            <input type="submit" value="Update Task" class="btn btn-primary">
        </form>
        @endif

        <h2>New Task</h2>
        <!-- form create task -->
    </div>
# 6
$ vendor/bin/phpunit
Hasil: Error
A request to [http://localhost/tasks/1] failed. Received status code [404].
Penyebab

Kita belum membuat route untuk melayani action update record task.

Solusi

Tambahkan route Route::patch('tasks/{task}', 'TasksController@update') pada routes/web.php.

<?php
// routes/web.php

// ..

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

// ..

Selanjutnya buat method TasksController@update langsung dengan script update record task dan redirect ke halaman /tasks.

<?php

// TasksController
    
    // ... method store
    
    public function update(Task $task)
    {
        // dd(request()->all());
        $task->update(request()->only('name', 'description'));

        return redirect('/tasks');
    }
# 7
$ vendor/bin/phpunit

Passed Edit Task

Yeyy.. Hijauuuu!!.

Baik. sampai di sini karena sudah dapat Hijau, kita coba tambahkan validasi dan refactor sedikit controllernya.

<?php

// TasksController
    
    // ... method store
    
    public function update(Task $task)
    {
        $taskData = request()->validate([
            'name'        => 'required|max:255',
            'description' => 'required|max:255',
        ]);

        $task->update($taskData);

        return redirect('/tasks');
    }
# 8
$ vendor/bin/phpunit

Seharusnya kita tetap dapat hasil OK (7 tests, 31 assertions). Sekarang kita coba lihat melalui browser.

Tampilan Edit Task

Hmmm.. sepertinya perlu sedikit kita perbaiki. Bagaimana kalau kita buat seperti ini?

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

    <div class="col-md-4">
        @if (count($errors) > 0)
            <div class="alert alert-danger">
                <ul class="list-unstyled">
                    @foreach ($errors->all() as $error)
                        <li>{% raw" >}}{{ $error }}{% endraw" >}}</li>
                    @endforeach
                </ul>
            </div>
        @endif
        @if (! is_null($editableTask) && request('action') == 'edit')
        <h2>Edit Task {% raw" >}}{{ $editableTask->name }}{% endraw" >}}</h2>
        <form id="edit_task_{% raw" >}}{{ $editableTask->id }}{% endraw" >}}" action="{% raw" >}}{{ url('tasks/'.$editableTask->id) }}{% endraw" >}}" method="post">
            {% raw" >}}{{ csrf_field() }}{% endraw" >}}
            {% raw" >}}{{ method_field('patch') }}{% endraw" >}}
            <div class="form-group">
                <label for="name" class="control-label">Name</label>
                <input id="name" name="name" class="form-control" type="text" value="{% raw" >}}{{ old('name', $editableTask->name) }}{% endraw" >}}">
            </div>
            <div class="form-group">
                <label for="description" class="control-label">Description</label>
                <textarea id="description" name="description" class="form-control">{% raw" >}}{{ old('description', $editableTask->description) }}{% endraw" >}}</textarea>
            </div>
            <input type="submit" value="Update Task" class="btn btn-primary">
            <a href="{% raw" >}}{{ url('tasks') }}{% endraw" >}}" class="btn btn-default">Cancel</a>
        </form>
        @else
        <h2>New Task</h2>
        <form action="{% raw" >}}{{ url('tasks') }}{% endraw" >}}" method="post">
            {% raw" >}}{{ csrf_field() }}{% endraw" >}}
            <div class="form-group">
                <label for="name" class="control-label">Name</label>
                <input id="name" name="name" class="form-control" type="text" value="{% raw" >}}{{ old('name') }}{% endraw" >}}">
            </div>
            <div class="form-group">
                <label for="description" class="control-label">Description</label>
                <textarea id="description" name="description" class="form-control">{% raw" >}}{{ old('description') }}{% endraw" >}}</textarea>
            </div>
            <input type="submit" value="Create Task" class="btn btn-primary">
        </form>
        @endif
    </div>

Hasilnya seperti di bawah ini, dimana form Create Task tidak muncul saat edit task.

Perbaikan Daftar Task

Terakhir, jalankan PHPUnit untuk memastikan tidak ada yang “rusak” setelah kita mengubah form view tadi.

# 9
$ vendor/bin/phpunit

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

Baik sampai di sini kita membuat form edit task dengan bantuan Testing Laravel. Pada project ini kita menampilkan halaman edit form pada view tasks.index. Teman-teman bebas jika ingin membuat halaman sendiri untuk form create dan edit tasknya.

Jika ada yang kurang dipahami pada artikel kali ini, silakan teman-teman sampaikan dikomentar. Selanjutnya kita akan membahas tentang menghapus task dengan Form Button. Terima kasih atas waktunya.