Tutorial Laravel API: Cara Membuat dan Menguji API RESTful

Diterbitkan: 2022-03-11

Dengan munculnya pengembangan seluler dan kerangka kerja JavaScript, menggunakan RESTful API adalah opsi terbaik untuk membangun satu antarmuka antara data Anda dan klien Anda.

Laravel adalah kerangka kerja PHP yang dikembangkan dengan mempertimbangkan produktivitas pengembang PHP. Ditulis dan dikelola oleh Taylor Otwell, kerangka kerja ini sangat berpendirian dan berusaha untuk menghemat waktu pengembang dengan lebih menyukai konvensi daripada konfigurasi. Kerangka kerja ini juga bertujuan untuk berkembang dengan web dan telah memasukkan beberapa fitur dan ide baru di dunia pengembangan web—seperti antrian pekerjaan, otentikasi API di luar kotak, komunikasi waktu nyata, dan banyak lagi.

Tutorial API Laravel - Membangun layanan Web yang RESTful

Dalam tutorial ini, kita akan menjelajahi cara Anda dapat membangun—dan menguji—API yang kuat menggunakan Laravel dengan autentikasi. Kami akan menggunakan Laravel 5.4, dan semua kode tersedia untuk referensi di GitHub.

API RESTful

Pertama, kita perlu memahami apa sebenarnya yang dianggap sebagai RESTful API. REST adalah singkatan dari REpresentational State Transfer dan merupakan gaya arsitektur untuk komunikasi jaringan antar aplikasi, yang bergantung pada protokol stateless (biasanya HTTP) untuk interaksi.

Kata Kerja HTTP Mewakili Tindakan

Dalam RESTful API, kami menggunakan kata kerja HTTP sebagai tindakan, dan titik akhir adalah sumber daya yang ditindaklanjuti. Kami akan menggunakan kata kerja HTTP untuk arti semantiknya:

  • GET : ambil sumber daya
  • POST : buat sumber daya
  • PUT : perbarui sumber daya
  • DELETE : hapus sumber daya

Kata kerja HTTP: GET, POST, PUT dan DELETE adalah tindakan di RESTful APIs

Perbarui Tindakan: PUT vs. POST

API RESTful adalah masalah yang diperdebatkan dan ada banyak pendapat di luar sana tentang apakah yang terbaik untuk memperbarui dengan POST , PATCH , atau PUT , atau jika tindakan buat sebaiknya diserahkan kepada kata kerja PUT . Pada artikel ini kita akan menggunakan PUT untuk tindakan pembaruan, karena menurut RFC HTTP, PUT berarti membuat/memperbarui sumber daya di lokasi tertentu. Persyaratan lain untuk kata kerja PUT adalah idempotence, yang dalam hal ini pada dasarnya berarti Anda dapat mengirim permintaan itu 1, 2 atau 1000 kali dan hasilnya akan sama: satu sumber daya yang diperbarui dalam database.

Sumber daya

Sumber daya akan menjadi target tindakan, dalam kasus kami Artikel dan Pengguna, dan mereka memiliki titik akhir sendiri:

  • /articles
  • /users

Dalam tutorial api laravel ini, sumber daya akan memiliki representasi 1:1 pada model data kami, tetapi itu bukan keharusan. Anda dapat memiliki sumber daya yang diwakili dalam lebih dari satu model data (atau tidak diwakili sama sekali dalam database) dan model yang sepenuhnya terlarang bagi pengguna. Pada akhirnya, Anda dapat memutuskan bagaimana merancang sumber daya dan model dengan cara yang sesuai dengan aplikasi Anda.

Catatan tentang Konsistensi

Keuntungan terbesar menggunakan seperangkat konvensi seperti REST adalah bahwa API Anda akan lebih mudah digunakan dan dikembangkan. Beberapa titik akhir cukup mudah dan, sebagai hasilnya, API Anda akan jauh lebih mudah digunakan dan dipelihara daripada memiliki titik akhir seperti GET /get_article?id_article=12 dan POST /delete_article?number=40 . Saya telah membangun API yang mengerikan seperti itu di masa lalu dan saya masih membenci diri saya sendiri karenanya.

Namun, akan ada kasus di mana akan sulit untuk memetakan ke skema Buat/Ambil/Perbarui/Hapus. Ingat bahwa URL tidak boleh mengandung kata kerja dan sumber daya tidak harus berupa baris dalam tabel. Hal lain yang perlu diingat adalah Anda tidak harus mengimplementasikan setiap tindakan untuk setiap sumber daya.

Menyiapkan Proyek Layanan Web Laravel

Seperti semua framework PHP modern, kita memerlukan Composer untuk menginstal dan menangani dependensi kita. Setelah Anda mengikuti petunjuk pengunduhan (dan menambahkan ke variabel lingkungan jalur Anda), instal Laravel menggunakan perintah:

 $ composer global require laravel/installer

Setelah instalasi selesai, Anda dapat membuat scaffolding aplikasi baru seperti ini:

 $ laravel new myapp

Untuk perintah di atas, Anda harus memiliki ~/composer/vendor/bin di $PATH Anda. Jika Anda tidak ingin berurusan dengan itu, Anda juga dapat membuat proyek baru menggunakan Komposer:

 $ composer create-project --prefer-dist laravel/laravel myapp

Dengan Laravel terinstal, Anda seharusnya dapat memulai server dan menguji apakah semuanya berfungsi:

 $ php artisan serve Laravel development server started: <http://127.0.0.1:8000> 

Saat Anda membuka localhost:8000 di browser Anda, Anda akan melihat halaman contoh Laravel

Saat Anda membuka localhost:8000 di browser Anda, Anda akan melihat halaman contoh ini.

Migrasi dan Model

Sebelum benar-benar menulis migrasi pertama Anda, pastikan Anda memiliki database yang dibuat untuk aplikasi ini dan tambahkan kredensialnya ke file .env yang terletak di root proyek.

 DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret

Anda juga dapat menggunakan Homestead, kotak Vagrant yang dibuat khusus untuk Laravel, tetapi itu sedikit di luar cakupan artikel ini. Jika Anda ingin tahu lebih banyak, lihat dokumentasi Homestead.

Mari kita mulai dengan model dan migrasi pertama kita—Artikel. Artikel harus memiliki judul dan bidang isi, serta tanggal pembuatan. Laravel menyediakan beberapa perintah melalui Artisan—alat baris perintah Laravel—yang membantu kita dengan membuat file dan meletakkannya di folder yang benar. Untuk membuat model Article, kita dapat menjalankan:

 $ php artisan make:model Article -m

Opsi -m adalah kependekan dari --migration dan opsi ini memberi tahu Artisan untuk membuatnya untuk model kita. Inilah migrasi yang dihasilkan:

 <?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateArticlesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('articles'); } }

Mari kita membedah ini sebentar:

  • Metode up() dan down() akan dijalankan saat kita melakukan migrasi dan rollback masing-masing;
  • $table->increments('id') menyiapkan bilangan bulat yang bertambah otomatis dengan nama id ;
  • $table->timestamps() akan menyiapkan stempel waktu untuk kita— created_at dan updated_at , tetapi jangan khawatir tentang pengaturan default, Laravel menangani pembaruan bidang ini saat diperlukan.
  • Dan akhirnya, Schema::dropIfExists() tentu saja akan menjatuhkan tabel jika ada.

Dengan menyingkir, mari tambahkan dua baris ke metode up() kita:

 public function up() { Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->text('body'); $table->timestamps(); }); }

Metode string() membuat kolom ekuivalen VARCHAR sementara text() membuat ekuivalen TEXT . Setelah itu selesai, mari kita lanjutkan dan migrasi:

 $ php artisan migrate

Anda juga dapat menggunakan opsi --step di sini, dan ini akan memisahkan setiap migrasi ke dalam kumpulannya sendiri sehingga Anda dapat memutarnya kembali satu per satu jika diperlukan.

Laravel out of the box hadir dengan dua migrasi, create_users_table dan create_password_resets_table . Kami tidak akan menggunakan tabel password_resets , tetapi menyiapkan tabel users untuk kami akan sangat membantu.

Sekarang mari kembali ke model kita dan tambahkan atribut tersebut ke bidang $fillable sehingga kita dapat menggunakannya di Article::create dan Article::update model:

 class Article extends Model { protected $fillable = ['title', 'body']; }

Bidang di dalam properti $fillable dapat ditetapkan secara massal menggunakan metode create() dan update() Eloquent. Anda juga dapat menggunakan properti $guarded , untuk mengizinkan semua kecuali beberapa properti.

Pembibitan Basis Data

Penyemaian basis data adalah proses mengisi basis data kita dengan data dummy yang dapat kita gunakan untuk mengujinya. Laravel hadir dengan Faker, perpustakaan hebat untuk menghasilkan format data dummy yang benar untuk kita. Jadi mari kita buat seeder pertama kita:

 $ php artisan make:seeder ArticlesTableSeeder

Seeder akan ditempatkan di direktori /database/seeds . Berikut tampilannya setelah kita mengaturnya untuk membuat beberapa artikel:

 class ArticlesTableSeeder extends Seeder { public function run() { // Let's truncate our existing records to start from scratch. Article::truncate(); $faker = \Faker\Factory::create(); // And now, let's create a few articles in our database: for ($i = 0; $i < 50; $i++) { Article::create([ 'title' => $faker->sentence, 'body' => $faker->paragraph, ]); } } }

Jadi mari kita jalankan perintah seed:

 $ php artisan db:seed --class=ArticlesTableSeeder

Mari kita ulangi proses untuk membuat seeder Pengguna:

 class UsersTableSeeder extends Seeder { public function run() { // Let's clear the users table first User::truncate(); $faker = \Faker\Factory::create(); // Let's make sure everyone has the same password and // let's hash it before the loop, or else our seeder // will be too slow. $password = Hash::make('toptal'); User::create([ 'name' => 'Administrator', 'email' => '[email protected]', 'password' => $password, ]); // And now let's generate a few dozen users for our app: for ($i = 0; $i < 10; $i++) { User::create([ 'name' => $faker->name, 'email' => $faker->email, 'password' => $password, ]); } } }

Kita dapat membuatnya lebih mudah dengan menambahkan seeder ke kelas DatabaseSeeder utama di dalam folder database/seeds :

 class DatabaseSeeder extends Seeder { public function run() { $this->call(ArticlesTableSeeder::class); $this->call(UsersTableSeeder::class); } }

Dengan cara ini, kita cukup menjalankan $ php artisan db:seed dan itu akan menjalankan semua kelas yang dipanggil dalam metode run() .

Rute dan Pengontrol

Mari buat titik akhir dasar untuk aplikasi kita: buat, ambil daftar, ambil satu, perbarui, dan hapus. Pada file routes/api.php , kita cukup melakukan ini:

 Use App\Article; Route::get('articles', function() { // If the Content-Type and Accept headers are set to 'application/json', // this will return a JSON structure. This will be cleaned up later. return Article::all(); }); Route::get('articles/{id}', function($id) { return Article::find($id); }); Route::post('articles', function(Request $request) { return Article::create($request->all); }); Route::put('articles/{id}', function(Request $request, $id) { $article = Article::findOrFail($id); $article->update($request->all()); return $article; }); Route::delete('articles/{id}', function($id) { Article::find($id)->delete(); return 204; })

Rute di dalam api.php akan diawali dengan /api/ dan middleware pelambatan API akan secara otomatis diterapkan ke rute ini (jika Anda ingin menghapus awalan, Anda dapat mengedit kelas RouteServiceProvider di /app/Providers/RouteServiceProvider.php ).

Sekarang mari kita pindahkan kode ini ke Controller-nya sendiri:

 $ php artisan make:controller ArticleController

ArticleController.php:

 use App\Article; class ArticleController extends Controller { public function index() { return Article::all(); } public function show($id) { return Article::find($id); } public function store(Request $request) { return Article::create($request->all()); } public function update(Request $request, $id) { $article = Article::findOrFail($id); $article->update($request->all()); return $article; } public function delete(Request $request, $id) { $article = Article::findOrFail($id); $article->delete(); return 204; } }

File routes/api.php :

 Route::get('articles', 'ArticleController@index'); Route::get('articles/{id}', 'ArticleController@show'); Route::post('articles', 'ArticleController@store'); Route::put('articles/{id}', 'ArticleController@update'); Route::delete('articles/{id}', 'ArticleController@delete');

Kami dapat meningkatkan titik akhir dengan menggunakan pengikatan model rute implisit. Dengan cara ini, Laravel akan menyuntikkan instance Article dalam metode kami dan secara otomatis mengembalikan 404 jika tidak ditemukan. Kita harus membuat perubahan pada file route dan controller:

 Route::get('articles', 'ArticleController@index'); Route::get('articles/{article}', 'ArticleController@show'); Route::post('articles', 'ArticleController@store'); Route::put('articles/{article}', 'ArticleController@update'); Route::delete('articles/{article}', 'ArticleController@delete');
 class ArticleController extends Controller { public function index() { return Article::all(); } public function show(Article $article) { return $article; } public function store(Request $request) { $article = Article::create($request->all()); return response()->json($article, 201); } public function update(Request $request, Article $article) { $article->update($request->all()); return response()->json($article, 200); } public function delete(Article $article) { $article->delete(); return response()->json(null, 204); } }

Catatan tentang Kode Status HTTP dan Format Respons

Kami juga telah menambahkan panggilan response()->json() ke titik akhir kami. Ini memungkinkan kami secara eksplisit mengembalikan data JSON serta mengirim kode HTTP yang dapat diuraikan oleh klien. Kode paling umum yang akan Anda kembalikan adalah:

  • 200 : Oke. Kode sukses standar dan opsi default.
  • 201 : Objek dibuat. Berguna untuk tindakan store .
  • 204 : Tidak ada konten. Ketika suatu tindakan berhasil dijalankan, tetapi tidak ada konten untuk dikembalikan.
  • 206 : Konten sebagian. Berguna ketika Anda harus mengembalikan daftar sumber daya yang diberi halaman.
  • 400 : Permintaan buruk. Opsi standar untuk permintaan yang gagal lolos validasi.
  • 401 : Tidak sah. Pengguna perlu diautentikasi.
  • 403 : Dilarang. Pengguna diautentikasi, tetapi tidak memiliki izin untuk melakukan suatu tindakan.
  • 404 : Tidak ditemukan. Ini akan dikembalikan secara otomatis oleh Laravel ketika sumber daya tidak ditemukan.
  • 500 : Kesalahan server internal. Idealnya Anda tidak akan secara eksplisit mengembalikan ini, tetapi jika sesuatu yang tidak terduga rusak, inilah yang akan diterima pengguna Anda.
  • 503 : Layanan tidak tersedia. Cukup jelas, tetapi juga kode lain yang tidak akan dikembalikan secara eksplisit oleh aplikasi.

Mengirim Respons 404 yang Benar

Jika Anda mencoba mengambil sumber daya yang tidak ada, Anda akan mendapatkan pengecualian dan Anda akan menerima seluruh stacktrace, seperti ini:

NotFoundHttpException Stacktrace

Kami dapat memperbaikinya dengan mengedit kelas exception handler kami, yang terletak di app/Exceptions/Handler.php , untuk mengembalikan respons JSON:

 public function render($request, Exception $exception) { // This will replace our 404 response with // a JSON response. if ($exception instanceof ModelNotFoundException) { return response()->json([ 'error' => 'Resource not found' ], 404); } return parent::render($request, $exception); }

Berikut contoh pengembaliannya:

 { data: "Resource not found" }

Jika Anda menggunakan Laravel untuk menyajikan halaman lain, Anda harus mengedit kode agar berfungsi dengan header Accept , jika tidak, 404 kesalahan dari permintaan reguler juga akan mengembalikan JSON.

 public function render($request, Exception $exception) { // This will replace our 404 response with // a JSON response. if ($exception instanceof ModelNotFoundException && $request->wantsJson()) { return response()->json([ 'data' => 'Resource not found' ], 404); } return parent::render($request, $exception); }

Dalam hal ini, permintaan API akan membutuhkan header Accept: application/json .

Autentikasi

Ada banyak cara untuk mengimplementasikan Otentikasi API di Laravel (salah satunya adalah Paspor, cara yang bagus untuk mengimplementasikan OAuth2), tetapi dalam artikel ini, kami akan mengambil pendekatan yang sangat disederhanakan.

Untuk memulai, kita perlu menambahkan bidang api_token ke tabel users :

 $ php artisan make:migration --table=users adds_api_token_to_users_table

Dan kemudian terapkan migrasi:

 public function up() { Schema::table('users', function (Blueprint $table) { $table->string('api_token', 60)->unique()->nullable(); }); } public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn(['api_token']); }); }

Setelah itu, jalankan saja migrasi menggunakan:

 $ php artisan migrate

Membuat Titik Akhir Registrasi

Kami akan menggunakan RegisterController (di folder Auth ) untuk mengembalikan respons yang benar saat pendaftaran. Laravel hadir dengan otentikasi di luar kotak, tetapi kita masih perlu sedikit mengubahnya untuk mengembalikan respons yang kita inginkan.

Jika API dalam bahasa Inggris, percakapan otentikasi api akan terdengar seperti ini

Pengontrol memanfaatkan sifat RegistersUsers untuk mengimplementasikan registrasi. Berikut cara kerjanya:

 public function register(Request $request) { // Here the request is validated. The validator method is located // inside the RegisterController, and makes sure the name, email // password and password_confirmation fields are required. $this->validator($request->all())->validate(); // A Registered event is created and will trigger any relevant // observers, such as sending a confirmation email or any // code that needs to be run as soon as the user is created. event(new Registered($user = $this->create($request->all()))); // After the user is created, he's logged in. $this->guard()->login($user); // And finally this is the hook that we want. If there is no // registered() method or it returns null, redirect him to // some other URL. In our case, we just need to implement // that method to return the correct response. return $this->registered($request, $user) ?: redirect($this->redirectPath()); }

Kita hanya perlu mengimplementasikan metode registered() di RegisterController kita. Metode menerima $request dan $user , jadi hanya itu yang kami inginkan. Begini tampilan metode di dalam pengontrol:

 protected function registered(Request $request, $user) { $user->generateToken(); return response()->json(['data' => $user->toArray()], 201); }

Dan kami dapat menautkannya di file rute:

 Route::post('register', 'Auth\RegisterController@register');

Pada bagian di atas, kami menggunakan metode pada model Pengguna untuk menghasilkan token. Ini berguna sehingga kami hanya memiliki satu cara untuk menghasilkan token. Tambahkan metode berikut ke model Pengguna Anda:

 class User extends Authenticatable { ... public function generateToken() { $this->api_token = str_random(60); $this->save(); return $this->api_token; } }

Dan itu saja. Pengguna sekarang terdaftar dan berkat validasi Laravel dan otentikasi yang siap pakai, bidang name , email , password , dan password_confirmation sandi_konfirmasi diperlukan, dan umpan balik ditangani secara otomatis. Periksa metode validator() di dalam RegisterController untuk melihat bagaimana aturan diterapkan.

Inilah yang kami dapatkan ketika kami mencapai titik akhir itu:

 $ curl -X POST http://localhost:8000/api/register \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{"name": "John", "email": "[email protected]", "password": "toptal123", "password_confirmation": "toptal123"}'
 { "data": { "api_token":"0syHnl0Y9jOIfszq11EC2CBQwCfObmvscrZYo5o2ilZPnohvndH797nDNyAT", "created_at": "2017-06-20 21:17:15", "email": "[email protected]", "id": 51, "name": "John", "updated_at": "2017-06-20 21:17:15" } }

Membuat Titik Akhir Masuk

Sama seperti titik akhir pendaftaran, kita dapat mengedit LoginController (di folder Auth ) untuk mendukung otentikasi API kita. Metode login dari sifat AuthenticatesUsers dapat diganti untuk mendukung API kami:

 public function login(Request $request) { $this->validateLogin($request); if ($this->attemptLogin($request)) { $user = $this->guard()->user(); $user->generateToken(); return response()->json([ 'data' => $user->toArray(), ]); } return $this->sendFailedLoginResponse($request); }

Dan kami dapat menautkannya di file rute:

 Route::post('login', 'Auth\LoginController@login');

Sekarang, dengan asumsi seeder telah dijalankan, inilah yang kami dapatkan ketika kami mengirim permintaan POST ke rute itu:

 $ curl -X POST localhost:8000/api/login \ -H "Accept: application/json" \ -H "Content-type: application/json" \ -d "{\"email\": \"[email protected]\", \"password\": \"toptal\" }"
 { "data": { "id":1, "name":"Administrator", "email":"[email protected]", "created_at":"2017-04-25 01:05:34", "updated_at":"2017-04-25 02:50:40", "api_token":"Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw" } }

Untuk mengirimkan token dalam request, Anda dapat melakukannya dengan mengirimkan atribut api_token di payload atau sebagai bearer token di header request dalam bentuk Authorization: Bearer Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw .

Keluar

Dengan strategi kami saat ini, jika token salah atau hilang, pengguna harus menerima respons yang tidak diautentikasi (yang akan kami terapkan di bagian selanjutnya). Jadi untuk titik akhir logout sederhana, kami akan mengirimkan token dan itu akan dihapus di database.

routes/api.php :

 Route::post('logout', 'Auth\LoginController@logout');

Auth\LoginController.php :

 public function logout(Request $request) { $user = Auth::guard('api')->user(); if ($user) { $user->api_token = null; $user->save(); } return response()->json(['data' => 'User logged out.'], 200); }

Dengan menggunakan strategi ini, token apa pun yang dimiliki pengguna akan menjadi tidak valid, dan API akan menolak akses (menggunakan middlewares, seperti yang dijelaskan di bagian selanjutnya). Ini perlu dikoordinasikan dengan front-end untuk menghindari pengguna tetap masuk tanpa memiliki akses ke konten apa pun.

Menggunakan Middlewares untuk Membatasi Akses

Dengan api_token yang dibuat, kita dapat mengaktifkan middleware otentikasi di file rute:

 Route::middleware('auth:api') ->get('/user', function (Request $request) { return $request->user(); });

Kita dapat mengakses pengguna saat ini menggunakan metode $request->user() atau melalui fasad Auth

 Auth::guard('api')->user(); // instance of the logged user Auth::guard('api')->check(); // if a user is authenticated Auth::guard('api')->id(); // the id of the authenticated user

Dan kita mendapatkan hasil seperti ini:

Sebuah InvalidArgumentException Stacktrace

Ini karena kita perlu mengedit metode yang unauthenticated saat ini di kelas Handler kita. Versi saat ini mengembalikan JSON hanya jika permintaan memiliki header Accept: application/json , jadi mari kita ubah:

 protected function unauthenticated($request, AuthenticationException $exception) { return response()->json(['error' => 'Unauthenticated'], 401); }

Dengan perbaikan itu, kita dapat kembali ke titik akhir artikel untuk membungkusnya di middleware auth:api . Kita dapat melakukannya dengan menggunakan grup rute:

 Route::group(['middleware' => 'auth:api'], function() { Route::get('articles', 'ArticleController@index'); Route::get('articles/{article}', 'ArticleController@show'); Route::post('articles', 'ArticleController@store'); Route::put('articles/{article}', 'ArticleController@update'); Route::delete('articles/{article}', 'ArticleController@delete'); });

Dengan cara ini kita tidak perlu mengatur middleware untuk setiap rute. Ini tidak menghemat banyak waktu sekarang, tetapi seiring pertumbuhan proyek, ini membantu menjaga rute KERING.

Menguji Titik Akhir Kami

Laravel menyertakan integrasi dengan PHPUnit di luar kotak dengan phpunit.xml yang sudah disiapkan. Kerangka kerja ini juga memberi kita beberapa pembantu dan pernyataan tambahan yang membuat hidup kita lebih mudah, terutama untuk menguji API.

Ada sejumlah alat eksternal yang dapat Anda gunakan untuk menguji API Anda; namun, pengujian di dalam Laravel adalah alternatif yang jauh lebih baik—kita dapat memperoleh semua manfaat dari pengujian struktur dan hasil API sambil mempertahankan kontrol penuh atas database. Untuk titik akhir daftar, misalnya, kita dapat menjalankan beberapa pabrik dan menegaskan bahwa respons berisi sumber daya tersebut.

Untuk memulai, kita perlu mengubah beberapa pengaturan untuk menggunakan database SQLite dalam memori. Menggunakan itu akan membuat pengujian kami berjalan secepat kilat, tetapi kerugiannya adalah beberapa perintah migrasi (batasan, misalnya) tidak akan berfungsi dengan baik dalam pengaturan tertentu. Saya menyarankan untuk menjauh dari SQLite dalam pengujian ketika Anda mulai mendapatkan kesalahan migrasi atau jika Anda lebih suka serangkaian pengujian yang lebih kuat daripada kinerja yang berjalan.

Kami juga akan menjalankan migrasi sebelum setiap pengujian. Pengaturan ini akan memungkinkan kita untuk membangun database untuk setiap pengujian dan kemudian menghancurkannya, menghindari segala jenis ketergantungan antar pengujian.

Dalam file config/database.php kita, kita perlu mengatur bidang database dalam konfigurasi sqlite ke :memory: :

 ... 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], ... ]

Kemudian aktifkan SQLite di phpunit.xml dengan menambahkan variabel lingkungan DB_CONNECTION :

 <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="DB_CONNECTION" value="sqlite"/> </php>

Dengan itu, yang tersisa hanyalah mengonfigurasi kelas TestCase dasar kami untuk menggunakan migrasi dan menyemai database sebelum setiap pengujian. Untuk melakukannya, kita perlu menambahkan sifat DatabaseMigrations , dan kemudian menambahkan panggilan Artisan pada metode setUp() kita. Inilah kelas setelah perubahan:

 use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; use Illuminate\Support\Facades\Artisan; abstract class TestCase extends BaseTestCase { use CreatesApplication, DatabaseMigrations; public function setUp() { parent::setUp(); Artisan::call('db:seed'); } }

Satu hal terakhir yang ingin saya lakukan adalah menambahkan perintah tes ke composer.json :

 "scripts": { "test" : [ "vendor/bin/phpunit" ], ... },

Perintah tes akan tersedia seperti ini:

 $ composer test

Menyiapkan Pabrik untuk Pengujian Kami

Pabrik akan memungkinkan kita membuat objek dengan cepat dengan data yang tepat untuk pengujian. Mereka berada di folder database/factories . Laravel keluar dari kotak dengan pabrik untuk kelas User , jadi mari tambahkan satu untuk kelas Article :

 $factory->define(App\Article::class, function (Faker\Generator $faker) { return [ 'title' => $faker->sentence, 'body' => $faker->paragraph, ]; });

Pustaka Faker sudah disuntikkan untuk membantu kami membuat format data acak yang benar untuk model kami.

Tes Pertama Kami

Kita dapat menggunakan metode assert Laravel untuk mencapai titik akhir dengan mudah dan mengevaluasi responsnya. Mari kita buat tes pertama kita, tes login, menggunakan perintah berikut:

 $ php artisan make:test Feature/LoginTest

Dan inilah ujian kami:

 class LoginTest extends TestCase { public function testRequiresEmailAndLogin() { $this->json('POST', 'api/login') ->assertStatus(422) ->assertJson([ 'email' => ['The email field is required.'], 'password' => ['The password field is required.'], ]); } public function testUserLoginsSuccessfully() { $user = factory(User::class)->create([ 'email' => '[email protected]', 'password' => bcrypt('toptal123'), ]); $payload = ['email' => '[email protected]', 'password' => 'toptal123']; $this->json('POST', 'api/login', $payload) ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'email', 'created_at', 'updated_at', 'api_token', ], ]); } }

Metode ini menguji beberapa kasus sederhana. Metode json() mencapai titik akhir dan pernyataan lainnya cukup jelas. Satu detail tentang assertJson() : metode ini mengubah respons menjadi pencarian array untuk argumen, jadi urutannya penting. Anda dapat menghubungkan beberapa panggilan assertJson() dalam kasus itu.

Sekarang, mari buat tes titik akhir register dan tulis pasangan untuk titik akhir itu:

 $ php artisan make:test RegisterTest
 class RegisterTest extends TestCase { public function testsRegistersSuccessfully() { $payload = [ 'name' => 'John', 'email' => '[email protected]', 'password' => 'toptal123', 'password_confirmation' => 'toptal123', ]; $this->json('post', '/api/register', $payload) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'email', 'created_at', 'updated_at', 'api_token', ], ]);; } public function testsRequiresPasswordEmailAndName() { $this->json('post', '/api/register') ->assertStatus(422) ->assertJson([ 'name' => ['The name field is required.'], 'email' => ['The email field is required.'], 'password' => ['The password field is required.'], ]); } public function testsRequirePasswordConfirmation() { $payload = [ 'name' => 'John', 'email' => '[email protected]', 'password' => 'toptal123', ]; $this->json('post', '/api/register', $payload) ->assertStatus(422) ->assertJson([ 'password' => ['The password confirmation does not match.'], ]); } }

Dan terakhir, titik akhir logout:

 $ php artisan make:test LogoutTest
 class LogoutTest extends TestCase { public function testUserIsLoggedOutProperly() { $user = factory(User::class)->create(['email' => '[email protected]']); $token = $user->generateToken(); $headers = ['Authorization' => "Bearer $token"]; $this->json('get', '/api/articles', [], $headers)->assertStatus(200); $this->json('post', '/api/logout', [], $headers)->assertStatus(200); $user = User::find($user->id); $this->assertEquals(null, $user->api_token); } public function testUserWithNullToken() { // Simulating login $user = factory(User::class)->create(['email' => '[email protected]']); $token = $user->generateToken(); $headers = ['Authorization' => "Bearer $token"]; // Simulating logout $user->api_token = null; $user->save(); $this->json('get', '/api/articles', [], $headers)->assertStatus(401); } }

Penting untuk dicatat bahwa, selama pengujian, aplikasi Laravel tidak dipakai lagi pada permintaan baru. Artinya, ketika kita menekan middleware otentikasi, itu menyimpan pengguna saat ini di dalam instance TokenGuard untuk menghindari memukul database lagi. Namun, pilihan yang bijak—dalam hal ini, itu berarti kita harus membagi tes logout menjadi dua, untuk menghindari masalah apa pun dengan pengguna yang di-cache sebelumnya.

Menguji titik akhir Artikel juga mudah:

 class ArticleTest extends TestCase { public function testsArticlesAreCreatedCorrectly() { $user = factory(User::class)->create(); $token = $user->generateToken(); $headers = ['Authorization' => "Bearer $token"]; $payload = [ 'title' => 'Lorem', 'body' => 'Ipsum', ]; $this->json('POST', '/api/articles', $payload, $headers) ->assertStatus(200) ->assertJson(['id' => 1, 'title' => 'Lorem', 'body' => 'Ipsum']); } public function testsArticlesAreUpdatedCorrectly() { $user = factory(User::class)->create(); $token = $user->generateToken(); $headers = ['Authorization' => "Bearer $token"]; $article = factory(Article::class)->create([ 'title' => 'First Article', 'body' => 'First Body', ]); $payload = [ 'title' => 'Lorem', 'body' => 'Ipsum', ]; $response = $this->json('PUT', '/api/articles/' . $article->id, $payload, $headers) ->assertStatus(200) ->assertJson([ 'id' => 1, 'title' => 'Lorem', 'body' => 'Ipsum' ]); } public function testsArtilcesAreDeletedCorrectly() { $user = factory(User::class)->create(); $token = $user->generateToken(); $headers = ['Authorization' => "Bearer $token"]; $article = factory(Article::class)->create([ 'title' => 'First Article', 'body' => 'First Body', ]); $this->json('DELETE', '/api/articles/' . $article->id, [], $headers) ->assertStatus(204); } public function testArticlesAreListedCorrectly() { factory(Article::class)->create([ 'title' => 'First Article', 'body' => 'First Body' ]); factory(Article::class)->create([ 'title' => 'Second Article', 'body' => 'Second Body' ]); $user = factory(User::class)->create(); $token = $user->generateToken(); $headers = ['Authorization' => "Bearer $token"]; $response = $this->json('GET', '/api/articles', [], $headers) ->assertStatus(200) ->assertJson([ [ 'title' => 'First Article', 'body' => 'First Body' ], [ 'title' => 'Second Article', 'body' => 'Second Body' ] ]) ->assertJsonStructure([ '*' => ['id', 'body', 'title', 'created_at', 'updated_at'], ]); } }

Langkah selanjutnya

Itu saja. Pasti ada ruang untuk perbaikan—Anda dapat mengimplementasikan OAuth2 dengan paket Passport, mengintegrasikan pagination dan lapisan transformasi (saya sarankan Fractal), daftarnya terus berlanjut—tetapi saya ingin membahas dasar-dasar pembuatan dan pengujian API di Laravel tanpa paket eksternal.

Pengembangan Laravel tentu saja meningkatkan pengalaman saya dengan PHP dan kemudahan pengujian dengannya telah memperkuat minat saya pada kerangka kerja. Ini tidak sempurna, tetapi cukup fleksibel untuk memungkinkan Anda mengatasi masalahnya.

Jika Anda mendesain API publik, lihat 5 Aturan Emas untuk Desain API Web yang Hebat.

Terkait: Otentikasi Pengguna Penuh dan Kontrol Akses – Tutorial Paspor Laravel, Pt. 1