REST API Unit Testing Pada Laravel

Khairu Aqsara Sudirman

Khairu Aqsara Sudirman

Nov 07, 2019 — 9 mins read

Pasti teman-teman sudah tidak asing dengan dengan istilah REST API, bahkan mungkin sudah sangat sering menggunakannya maupun membuatnya, Tapi apakah sudah pernah melakukan Testing menggunakan phpunit? mungkin ada yang bertanya untuk apa ? kegunaanya apa ? toh aplikasinya berjalan dengan lancar tanpa ada masalah, ngapain repot-repot melakukan unit testing ? hm.... jika saya harus menjawab semua pertanyaan itu bakalan panjang banget, karena harus berteori ngalor dan ngidul untuk menjabarkannya, mungkin dilain kesempatan akan saya coba buat penjabaranya, tapi untuk kali ini anggap saja ngerepotin diri sambil belajar.

well, let's say kita udah bikin API, dan kita pengen buat unit testing untuk menguji fungsionalitas dari setiap endpoint yang sudah kita siapkan, saya pribadi biasanya akan menggunakn REST client untuk mencobanya, tapi kali ini kita akan mencoba menggunakan phpunit untuk mencobanya. agar lebih mudah dipahami kita coba dari awal saja

1. Install Laravel
2. Create database dan setup koneksinya di file .env

setelah laravel tersintall, pastikan kita sudah menginstall phpunit, biasanya pada saat kita menginstall laravel phpunit sudah ikut terinstall, untuk mencobanya cukup mengetik perintah berikut pada root directory projek laravel yang sudah kita install

./vendor/bin/phpunit
PHPUnit 8.4.3 by Sebastian Bergmann and contributors.
..                                                      2 / 2 (100%)
Time: 121 ms, Memory: 16.00 MB
OK (2 tests, 2 assertions)

jika hasilnya seperti diatas, phpunit sudah bisa kita panggil dan gunakan.Langkah berikutnya kita harus merubah konfigurasi phpunit untuk mengatur koneksi database dan beberapa konfigurasi lainya. buka file ./phpunit.xml dan sesuaikan konfigurasinya seperti dibawah

<php>
    <server name="APP_ENV" value="testing"/>
    <server name="BCRYPT_ROUNDS" value="4"/>
    <server name="CACHE_DRIVER" value="array"/>
    <server name="MAIL_DRIVER" value="array"/>
    <server name="QUEUE_CONNECTION" value="sync"/>
    <server name="SESSION_DRIVER" value="array"/>
    <env name="DB_CONNECTION" value="sqlite"/>
    <env name="APP_DEBUG" value="false"/>
</php>

kemudian kita buat satu buah Model yang akan kita uji

php artisan make:model Post -c -m -f
//hasilnya
Model created successfully.
Factory created successfully.
Created Migration: 2019_11_07_083430_create_posts_table
Controller created successfully.

parameter -c -m -f diatas kita gunakan untuk agar pada saat perintah artisan make:model dijalankan laravel secara otomatis akan sekaligus membuatkan Controller, Migration dan Factory File, dengan begitu kita tidak perlu menjalankan peritan artisan berkali-kali untuk membuat migrasi dan Controller.

selanjutnya kita hapus file migrasi yang ada di folder database/migrations yang berkaitan dengan user, karena kali ini kita tidak akan menggunakan authentication, hapus file-file berikut

xxx_create_user_table.php
xxx_create_password_resets_table.php
xxx_create_failed_jobs_table.php

disini saya menggunakan laravel terbaru sejak tulisan ini dibuat, yaitu versi 6.5, file xxx_create_failed_jobs_table.php mungkin tidak akan teman-teman temukan di versi laravel sebelumnya.

setelah file diatas dihapus, sekarang kita edit file migrasi hasil perintah sebelumnya, yaitu file 2019_11_07_083430_create_posts_table.php, sesuaikan isinya dengan baris kode dibawah

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostsTable extends Migration
{
     public function up(){
         Schema::create('posts', function (Blueprint $table) {
             $table->increments('id');
             $table->string('title');
             $table->text('content');
             $table->timestamps();
         });
     }
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

kemudian edit file /database/factories/PostFactory.php dan isi dengan script berikut

<?php
use App\Post;
use Faker\Generator as Faker;
$factory->define(Post::class, function (Faker $faker) {
  return [
      'title' => $faker->sentence,
      'content' => $faker->paragraph
  ];
});

file migrasi akan membuatkan tabel dengan nama posts didatabase, sedangkan file factory akan mengisi tabel tersebut dengan content dummy (Fake/Faker) untuk kebutuhan pengujian, untuk lebih jelas tentang Faker pada laravel teman-teman bisa membacanya di website resmi laravel.

mari kita lanjutkan, selanjutnya kita rubah file /app/Post.php dan tambahkan attribute protected $fillable

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
  protected $fillable = [
      'title',
      'content'
  ];
}

langkah berikutnya kita harus menentukan endpoint dan basic CRUD untuk REST API yang akan kita buat, pada file /app/Http/Controllers/PostController.php tambahkan baris kode berikut.

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Post;

class PostController extends Controller
{
  public function index() {
      return Post::all();
  }
  public function show(Post $post) {
      return $post;
  }
  public function store(Request $request) {
      $post = Post::create($request->all());
      return response()->json($post, 201);
  }
  public function update(Request $request, Post $post) {
      $post->update($request->all());
      return response()->json($post);
  }
  public function delete(Post $post) {
      $post->delete();
      return response()->json(null, 204);
  }
}

fungsi index() akan memberikan kita semua post yang ada, fungsi show() akan memberikan response singel post (satu post) berdasarkan id-nya, fungsi store() akan membuat record baru, fungsi update() dan delete() sesuai nama fungsinya, saya rasa teman-teman bisa memahami fungsi dari masing-masing method diatas.

agar fungsi-fungsi tersebut bisa dipanggil melalui URL kita harus menentukan endpoint dari masing-masing fungsi tersebut, untuk itu kita harus merubah isi file /routes/api.php 

<?php
Route::group(['prefix' => 'posts'], function() {
    Route::get('/', 'PostController@index')->name('posts');
    Route::get('/{post}', 'PostController@show')->name('posts.show');
    Route::post('/', 'PostController@store')->name('posts.store');
    Route::put('/{post}', 'PostController@update')->name('posts.update');
    Route::delete('/{post}', 'PostController@delete')->name('posts.delete');
});

seperti yang saya jelaskan sebelumnya, pada pengujian ini kita tidak menggunakan Authentication karena hanya untuk pembelajaran, semua REST diapanggil dengan menggunakan prefix /api/posts karena pada route diatas kita mendifinsikan sebuah prefix dengan nama posts. setelah semua beres selanjutnya kita jalankan perintah berikut

php artisan migrate
//Output
Migration table created successfully.
Migrating: 2019_11_07_083430_create_posts_table
Migrated:  2019_11_07_083430_create_posts_table (0 seconds)

dengan begitu kita sudah selesai membuat REST API sederhana, wait....bukanya judulnya Unit testing ya ? ya kan kita mulai dari awal kalo ga ada REST API nya, apanya yang mau di unit testingkan ?, untuk mencoba REST API kita tanpa unit testing teman-teman bisa menggunakan REST Client seperti Postman, Insomnia atau bahkan Httpie. berikut hasil pemanggilan REST API dengan menggunakan httpie

Pengujian dengan HTTPIE

Pengujian dengan HTTPIE

tentu saja hasilnya masih kosong, karena memang tidak ada record yang terdaftar. 

Unit Testing

well, setelah sekian panjang nya, akhirnya masuk bagian yang sesuai judul, mohon maaf jika sedikit membosankan, untuk teman-teman yang sudah punya REST API dan hanya ingin mencoba unit testingnya bisa skip bagian diawal-awal karena memang bagian awal hanya untuk membuat REST API nya saja, ok let's take a little dive into unit testing.

secara default Laravel sudah memberikan 2 test class pada folder /tests/Feature dan /test/Unit, kita akan memulainya dengan mengedit file /test/TestCase.php agar Class Factory yang kita buat sebelumnya bisa kita gunakan dalam pengujian nantinya.

<?php
namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Faker\Factory;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication, DatabaseMigrations;
    protected $faker;

    public function setUp(): void{
      parent::setUp();
      $this->faker = Factory::create();
    }
}

kemudian kita butuh satu file test baru, file test inilah yang nantinya akan kita gunakan untuk melakukan testing pada CRUD yang sudah kita buat sebelumnya, yang perlu dicatat, sejak laravel versi 5.8 untuk membuat setUp() test kita harus menambahkan : void , untuk membuat file test baru kita cukup menjalankan perintah 

php artisan make:test PostTest --unit

kenapa menggunakan parameter --unit ? karena kita menggunakan unit test bukan dusk test, untuk laravel dusk sendiri memiliki cara yang berbeda dengan unit test, kali ini kita tidak menggunakan dusk melainkan unit test.

buka file /test/Unit/PostTest.php dan sesuaikan dengan script dibawah ini.

<?php
namespace Tests\Unit;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Post;

class PostTest extends TestCase
{
  public function test_bisa_bikin_post(){
    $data = [
        'title' => $this->faker->sentence,
        'content' => $this->faker->paragraph,
    ];
    $this->post(route('posts.store'), $data)
        ->assertStatus(201)
        ->assertJson($data);
  }

  public function test_bisa_update_post(){
    $post = factory(Post::class)->create();
    $data = [
        'title' => $this->faker->sentence,
        'content' => $this->faker->paragraph
    ];
    $this->put(route('posts.update', $post->id), $data)
        ->assertStatus(200)
        ->assertJson($data);
  }

  public function test_bisa_show_post(){
    $post = factory(Post::class)->create();
    $this->get(route('posts.show', $post->id))
        ->assertStatus(200);
  }


  public function test_bisa_delete_post(){
    $post = factory(Post::class)->create();
    $this->delete(route('posts.delete', $post->id))
        ->assertStatus(204);
  }

  public function test_bisa_tampilkan_list_posts(){
    $posts = factory(Post::class, 2)->create()->map(function ($post) {
        return $post->only(['id', 'title', 'content']);
    });
    $this->get(route('posts'))
        ->assertStatus(200)
        ->assertJson($posts->toArray())
        ->assertJsonStructure([
            '*' => [ 'id', 'title', 'content' ],
        ]);
  }
}

jika kita perhatikan setiap method yang ada, semua method diawali dengan kata test_ ini adalah salah satu syarat pembuatan test, dengan begitu phpunit akan tau jika method tersebut merupakan test dan menajalankanya.

pada pengujian ini kita menggunakan trait RefreshDatabase artinya phpunit akan menjalankan perintah migrasi, maksudnya phpunit akan membuat tabel sesuai file migrasi kita sebelumnya, kemudian menjalan test dan akan menghapus kembali tabel tersebut.

pada baris kode diatas juga assertStatus(...) fungsi ini akan memeriksa respon status yang diterima sesuai dengan espektasi atau tidak, teman-teman juga bisa melihat ada fungsi assertJson(...) dimana fungsi ini akan memastikan jika respon yang diterima dalam format json yang valid, ada juga assertJsonStructure(...) fungsi ini memastikan jika respon json yang diterima memiliki attribute yang sesuai.

dengan memahami fungsi assert(...) saja kita suda bisa menebak cara kerja test yang sedang kita buat, sederhanya, kita membuat sebuah skenario test yang hasil dari test tersebut menggambarkan kerja valid dari REST API yang kita buat, seperti misalnya

  1. apakah fungsi create record pada rest api berjalan sesuai fungsinya ?
  2. apakah fungsi rest api untuk merubah record berjalan dengan fungsinya ?
  3. apakah fungsi rest api untuk menampilkan record berjalan sesuai fungsinya ? 
  4. apakah fungsi rest api untu menghapus record berjalan sesuai fungsinya ?

Berjalan sesuai fungsinya disini bisa kita artikan jika REST API yang sedang/sudah kita buat tidak memiliki kendala/error pada saat digunakan.

sekarang mari kita test CRUD REST API yang sudah kita buat sebelumnya dengan menjalankan phpunit

vendor/bin/phpunit
Running Test Dengan phpunit

Running Test Dengan phpunit

dari hasilnya diatas, kita bisa melihat jika test berhasil tanpa error, artinya fungsi CRUD REST API yang kita buat berjalan sesuai dengan fungsinya, jika teman-teman ingin hasil yang lebih detail, teman-teman bisa menggunakan opsi --verbose dan --debug pada phpunit

vendor/bin/phpunit --verbose --debug
phpunit debug dan verbose

phpunit debug dan verbose

dengan menggunakan unti testing kita akan lebih dimudahkan dalam melakukan pengujian setiap kali ada perubahan pada fungsi REST API yang kita buat, kita tidak perlu repot melakukan pengujian menggunakan REST Client, kita tidak perlu melakukan pengujain satu persatu per fungsi jika kita khawatir ada perubahan dan mempengaruhi fungsi-fungsi lainya. Dengan phpunit dan unit test kita akan sangat terbantu.

mudah-mudahan tulisan singkat ini bisa membantu teman-teman sedikit memahami cara melakukan test dengan phpunit dan unit test.

laravel php
Read More

Laravel Auth Redirection

Didalam laravel sendiri propertis ini diimplementasikan melalui trait RedirectUsers, trait ini bisa teman-teman temukan jika melihat kedalam folder sumber framework laravel

Read More

Pemrograman Object Oriented PHP Bagian I

OOP merupakan paradigma pemrograman yang berorientasikan kepada objek. Semua data dan fungsi di dalam paradigma ini dibungkus dalam kelas-kelas atau objek-objek.