Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. PHP

Testing Laravel Controller

by
Difficulty:AdvancedLength:LongLanguages:

Indonesian (Bahasa Indonesia) translation by Ari Gustiawan (you can also view the original English article)

Testing controller bukan hal yang termudah di dunia. Nah, biarkan aku ulang kata-kata yang: menguji mereka adalah menang; Apa itu sulit, setidaknya pada awalnya, adalah menentukan apa yang akan diuji.

Harus tes controller memverifikasi teks pada halaman? Harus itu menyentuh database? Harus itu memastikan bahwa variabel ada dalam view? Jika ini adalah jperjalanan pertama Anda, hal ini dapat membingungkan! Biarkan saya membantu.

Controller tes harus memverifikasi response, memastikan bahwa metode akses database benar dipicu, dan menegaskan bahwa variabel sesuai contoh dikirim ke view.

Proses pengujian controller dapat dibagi menjadi tiga bagian.

  • Isolate: Mock semua dependensi (mungkin termasuk View).
  • Call: Memicu metode controller yang diinginkan.
  • Ensure: Melakukan pernyataan, memverifikasi bahwa stage telah ditetapkan dengan benar.

Hello World controller Testing

Cara terbaik untuk belajar hal-hal ini adalah melalui contoh. Berikut ini adalah "hello world" controller pengujian di Laravel.

Laravel memanfaatkan segenggam Symfony's komponen untuk memudahkan proses pengujian route dan View, termasuk HttpKernel, DomCrawler, dan BrowserKit. Ini adalah mengapa sangat penting bahwa tes PHPUnit Anda mewarisi dari, tidak dari PHPUnit\_Framework\_TestCase, tapi dari TestCase. Jangan khawatir, Laravel masih extend itu, tapi hal itu membantu men-setup app Laravel untuk pengujian, serta menyediakan berbagai macam helper metode penegasan bahwa Anda dianjurkan untuk menggunakan. Lebih pada segera.

Dalam potongan kode di atas, kita membuat sebuah GET permintaan untuk /posts, atau localhost:8000 /posts. Dengan asumsi bahwa baris ini ditambahkan ke instalasi baru Laravel, Symfony akan melemparkan NotFoundHttpException. Jika bekerja bersama, mencobanya dengan menjalankan phpunit dari baris perintah.

Dalam human-speak, ini pada dasarnya diterjemahkan menjadi, "Hei, saya mencoba memanggil rute itu, tapi Anda tidak memiliki apa pun terdaftar, bodoh!"

Seperti yang dapat Anda bayangkan, ini jenis permintaan cukup umum ke titik bahwa masuk akal untuk menyediakan metode helper, seperti $this->call(). Pada kenyataannya, Laravel melakukan hal yang sama! Ini berarti bahwa contoh sebelumnya dapat direfractor, seperti:

Overloading adalah teman Anda

Meskipun kami akan tetap dengan fungsi dasar dalam bab ini, dalam proyek-proyek pribadi saya, saya mengambil hal-hal satu langkah lebih lanjut oleh memungkinkan untuk metode tersebut sebagai $this->get(), $this->post(), dll. Berkat PHP overloading, ini hanya membutuhkan penambahan metode tunggal, yang Anda dapat menambah app/tests/TestCase.php.

Sekarang, Anda bebas untuk menulis $this->get('posts') dan mencapai hasil yang sama sebagai dua contoh sebelumnya. Seperti disebutkan di atas, namun, mari kita tetap dengan fungsi dasar framework untuk lebih mudahnya.

Untuk membuat lulus tes, kita hanya perlu mempersiapkan route yang tepat.

Menjalankan phpunit lagi akan me-return green.


Laravel Helper Assertion

Tes yang Anda akan menemukan diri menulis berulang kali adalah salah satu yang menjamin bahwa kontroler melewati sebuah variabel tertentu ke view. Sebagai contoh, metode index dari PostsController harus pass variabel $posts ke view yang terkait, benar? Dengan cara itu, tampilan dapat menyaring melalui semua posting, dan menampilkannya pada halaman. Ini adalah merupakan tes untuk ditulis!

Jika itu task yang umum, kemudian, sekali lagi, tidak masuk akal untuk Laravel untuk memberikan helper assertion untuk mencapai hal ini? Tentu saja itu akan. Dan, tentu saja, Laravel juga!

Illuminate\Foundation\Testing\TestCase termasuk sejumlah metode yang secara drastis akan mengurangi jumlah kode yang diperlukan untuk melakukan pernyataan-pernyataan dasar. Daftar ini mencakup:

  • assertViewHas
  • assertResponseOk
  • assertRedirectedTo
  • assertRedirectedToRoute
  • assertRedirectedToAction
  • assertSessionHas
  • assertSessionHasErrors

Berikut contoh panggilan  GET /posts dan memverifikasi bahwa vieew yang menerima $posts variabel.

Tip: Ketika formatting, saya lebih suka untuk memberikan lne break antara pernyataan tes dan kode yang mempersiapkan stage.

assertViewHas mempunyaikemudahan yang memeriksa obyek respon - yang dikembalikan dari $this->call() - dan memverifikasi bahwa data yang terkait dengan tampilan yang mengandung sebuah variabel posts.

Ketika memeriksa objek respon, Anda memiliki dua pilihan inti.

  • $response->getOriginalContent(): mengambil konten asli, atau mengembalikan View. Opsional, Anda dapat mengakses properti original secara langsung, alih-alih memanggil metode getOriginalContent.
  • $response->getContent(): me-render output diberikan. Jika View instance return dari route, maka getContent() akan sama dengan HTML output. Ini dapat membantu untuk verifikasi DOM, seperti "tampilan harus mengandung string ini."

Mari kita asumsikan bahwa route posts terdiri dari:

Kita harus menjalankan phpunit, itu akan berkuak dengan pesan langkah berikutnya yang berguna:

Untuk membuatnya hijau, kami hanya mengambil posting dan menyebarkannya ke tampilan.

Satu hal yang perlu diingat adalah bahwa, seperti kode saat ini berdiri, itu hanya memastikan bahwa variabel, $posts, passing ke view. Itu tidak memeriksa value. AssertViewHas opsional menerima argumen kedua untuk memverifikasi nilai variabel, serta keberadaannya.

Dengan kode ini dimodifikasi, unles view memiliki sebuah variabel, $posts, yang sama dengan foo, tes akan gagal. Dalam situasi ini, walaupun, ini kemungkinan bahwa kita akan agak tidak menetapkan nilai, tetapi justru menyatakan bahwa nilai menjadi instance Laravel's Illuminate\Database\Eloquent\Collection kelas. Bagaimana kita bisa mencapai itu? PHPUnit menyediakan pernyataan assertInstanceOf berguna untuk mengisi kebutuhan ini!

Dengan modifikasi ini, kami telah menyatakan bahwa controller harus pass $posts - instance Illuminate\Database\Eloquent\Collection - ke view. Sangat baik.


Mocking Database

Ada satu masalah dengan pengujian kami sejauh ini. Apakah Anda menangkap itu?

Untuk setiap tes, SQL query yang dijalankan pada database. Meskipun ini berguna untuk beberapa jenis pengujian (acceptance, integraion), untuk controller dasar testing, itu hanya akan berfungsi untuk mengurangi kinerja.

Saya telah dibor ini ke tengkorak beberapa kali pada saat ini. Kami tidak tertarik dengan testing Eloquent kemampuan untuk mengambil record dari database. Ini memiliki tesnya sendiri. Taylor tahu itu bekerja! Mari kita tidak menyia-nyiakan waktu dan kekuatan pemrosesan mengulang tes yang sama.

Sebaliknya, lebih baik untuk mock database, dan hanya memverifikasi bahwa metode yang sesuai dipanggil dengan argumen-argumen yang benar. Atau, dengan kata lain, kami ingin memastikan bahwa Post::all() tidak dipanggil dan hits database. Kita tahu bahwa itu bekerja, sehingga tidak memerlukan pengujian.

Bagian ini akan sangat bergantung pada perpustakaan Mocked. Harap Tinjau bahwa bab dari buku saya, jika Anda tidak belum terbiasa dengan hal itu.

Diperlukan Refactoring

Sayangnya, sejauh ini, kami telah terstruktur kode dalam cara yang membuatnya hampir mustahil untuk menguji.

Inilah mengapa dianggap buruk praktek ke nested Eloquent panggilan ke controller Anda. Jangan bingung Laravel Facade, yang dapat diuji dan dapat ditukarkan dengan mock (Queue::shouldReceive()), dengan model Eloquent anda. Solusinya adalah untuk inject lapisan database ke controller melalui konstruktor. Hal ini memerlukan beberapa refactoring.

Warning: Menyimpan logika dalam rute callback berguna untuk proyek-proyek kecil dan api, tapi mereka membuat pengujian sangat sulit. Untuk aplikasi dari berbagai ukuran yang cukup besar, menggunakan controller.

Mari kita mendaftar sumber daya baru dengan mengganti rute posts dengan:

.. .dan membuat controller resourceful dengan Artisan.

Sekarang, daripada referensi Post model langsung, kami akan inject itu ke controller konstruktor. Berikut adalah contoh yang menghilangkan semua metode restful kecuali satu bahwa kami tertarik saat ini dalam testing.

Harap dicatat bahwa itu adalah ide yang baik untuk typehint antarmuka, daripada referensi model Eloquent, itu sendiri. Namun, satu hal pada suatu waktu! Mari kita bekerja untuk itu.

Ini adalah cara yang secara signifikan lebih baik untuk struktur kode. Karena model sekarang sudah di inject, kita memiliki kemampuan untuk swap keluar dengan versi mocked untuk pengujian. Berikut adalah contoh dari melakukan hal itu:

Benfit kunci untuk restrukturisasi ini adalah bahwa, sekarang, database tidak akan sia-sia terkena. Sebaliknya, menggunakan Mockery, kami hanya memverifikasi bahwa all metode dipicu pada model.

Sayangnya, jika Anda memilih untuk mengorbankan pengkodean untuk antarmuka, dan sebaliknya menyuntikkan Post model ke controller, sedikit tipu daya harus digunakan untuk mendapatkan Eloquent penggunaan Statika, yang dapat bentrokan dengan Mockery. Inilah sebabnya mengapa kita hijack Post dan Eloquent kelas dalam ujian konstruktor, sebelum versi resmi telah dibuka. Dengan cara ini, kita memiliki yang bersih untuk menyatakan harapan. The downside, tentu saja, adalah bahwa kita tidak bisa default untuk setiap metode yang ada, melalui penggunaan metode Mockery, seperti makePartial().

IoC Container

Laravel's IoC Container drastis memudahkan proses menyuntikkan dependensi ke dalam kelas Anda. Setiap kali sebuah controller yang diminta, itu diselesaikan dari IoC Container. Dengan demikian, ketika kita harus menyatakan bahwa versi mock Post harus digunakan untuk pengujian, kita hanya perlu memberikan Laravel dengan contoh Post yang harus digunakan.

Pikirkan kode ini mengatakan, "Hei Laravel, ketika Anda membutuhkan instance dari Post, saya ingin Anda untuk menggunakan versi mocked saya." Karena app extend Container, kita memiliki akses ke semua IoC metode langsung dari itu.

Berdasarkan Instansiasi controller, Laravel memanfaatkan kekuatan dari PHP refleksi untuk membaca typehint dan menyuntikkan ketergantungan untuk Anda. Benar; Anda tidak perlu menulis mengikat tunggal untuk memungkinkan ini; Hal ini otomatis!


Redirections

expectation umum lain bahwa Anda akan menemukan diri menulis adalah salah satu yang menjamin bahwa pengguna diarahkan ke lokasi yang tepat, mungkin setelah menambahkan posting baru ke database. Bagaimana mungkin kami mencapai ini?

Dengan asumsi bahwa kita mengikuti restfull, untuk menambahkan posting baru, kami akan POST ke koleksi, atau posts (jangan bingung metode permintaan POST dengan nama resource, yang kebetulan memiliki nama yang sama).

Kemudian, kita hanya perlu untuk memanfaatkan salah satu Laravel's helper , assertRedirectedToRoute.

Tip: Ketika resouce yang terdaftar dengan Laravel (Route::resource()), framework akan secara otomatis mendata nama route yang diperlukan. Menjalankan php artisan routes jika Anda lupa apa yang nama-nama ini.

Anda mungkin lebih suka juga memastikan bahwa $_POST superglobal dilewatkan ke metode create. Meskipun kita tidak secara langsung submit form, kita masih bisa memungkinkan untuk ini, melalui metode Input:: replace(), yang memungkinkan kita untuk "stub" array. Berikut adalah tes diubah, yang menggunakan metode Mockery with untuk memverifikasi argumen yang dilewatkan ke metode direferensikan oleh shouldReceive.


Path

Satu hal yang kita belum dipertimbangkan dalam tes ini adalah validasi. Harus ada dua jalur yang terpisah melalui metode store, tergantung pada apakah validasi berhasil:

  1. Mengarahkan kembali ke bentuk "Create Post", dan menampilkan kesalahan validasi form.
  2. Mengarahkan ulang ke koleksi, atau rute bernama, posts.index.

Sebagai best practice, setiap tes harus mewakili tapi satu path melalui kode Anda.

path pertama ini akan untuk validasi gagal.

Snipet kode di atas secara eksplisit menyatakan kesalahan yang harus ada. Atau, Anda dapat menghilangkan argumen ke assertSessionHasErrors, dalam hal ini hanya akan memverifikasi bahwa pesan tas telah flashed (dalam terjemahan, pengalihan Anda termasuk withErrors($errors)).

Sekarang untuk test yang menangani berhasil tervalidasi.

Kode produksi untuk kedua ujian mungkin terlihat seperti:

Perhatikan bagaimana Validator yang bersarang langsung di controller? Umumnya, saya akan merekomendasikan bahwa Anda abstrak ini pergi ke service. Dengan cara itu, Anda dapat menguji Anda validasi dalam isolasi dari controller atau rute. Namun, mari kita meninggalkan hal-hal seperti mereka demi kesederhanaan. Satu hal yang perlu diingat adalah bahwa kita tidak mocking Validator, meskipun Anda pasti bisa melakukannya. Karena kelas ini sebuah facade, itu dapat dengan mudah bertukar keluar dengan versi mocking, melalui metode shouldReceive Facade, tanpa kita perlu khawatir tentang menyuntikkan instance melalui konstruktor. Menang!

Dari waktu ke waktu, Anda akan menemukan bahwa metode yang perlu akan dipermainkan harus kembali objek, itu sendiri. Untungnya, dengan Mokcery, ini adalah sepotong kue: kita hanya perlu anonim mock, dan parsing array, yang sinyal metode nama dan respon nilai, masing-masing. Seperti:

akan mempersiapkan objek, yang mengandung fails() metode yang mengembalikan true.


Repositori

Untuk memungkinkan untuk hasil yang optimal fleksibilitas, daripada menciptakan sebuah link langsung antara controller dan ORM, seperti fasih, itu lebih baik untuk kode untuk sebuah interface. Keuntungan besar dari pendekatan ini adalah bahwa, jika Anda mungkin perlu swap keluar Eloquent untuk, katakanlah, Mongo atau Redis, melakukannya benar-benar membutuhkan modifikasi dari satu baris. Bahkan lebih baik, controller tidak pernah perlu disentuh.

Repositori mewakili layer akses data aplikasi Anda.

Apa yang mungkin sebuah antarmuka untuk mengelola lapisan database Post terlihat seperti? Ini harus Anda mulai.

Ini tentu saja dapat diperpanjang, tapi kami telah menambahkan metode minimal  untuk demo: all, find, dan create. Perhatikan bahwa interface repositori yang disimpan dalam app/repositories. Karena folder ini tidak otomatis diambil secara default, kita perlu untuk memperbarui file composer.json untuk aplikasi untuk referensi.

Ketika kelas baru ditambahkan ke direktori ini, jangan lupa untuk composer dump-autoload - o. -o, (mengoptimalkan) flag opsional, tetapi harus selalu digunakan, sebagai penerapan terbaik.

Jika Anda mencoba untuk inject interface ini ke controller, Laravel akan snap pada Anda. Silahkan mencobanya dan melihat. Berikut adalah PostController dimodifikasi, yang telah diperbarui untuk inject interface, daripada model Post Eloquent.

Jika Anda menjalankan server dan melihat output, Anda akan dipenuhi dengan kesalahan halaman Whoops (tapi indah) ini, menyatakan bahwa "PostRepositoryInterface tidak instantiable."

Not Instantiable

Jika Anda berpikir tentang hal itu, tentu saja framework adalah berkotek! Laravel cerdas, tetapi tidak pembaca pikiran. Perlu diberitahu implementasi interface yang harus digunakan di dalam controller.

Untuk sekarang, mari kita binding ke app/routes.php. Kemudian, kita akan sebaliknya membuat menggunakan service provider untuk menyimpan logika semacam ini.

Verbalisasi panggilan fungsi ini sebagai, "Laravel, bayi, ketika Anda membutuhkan sebuah instance dari PostRepositoryInterface, saya ingin Anda untuk menggunakan EloquentPostRepository."

app/repositori/EloquentPostRepository hanya akan bungkus sekitar Eloquent yang mengimplementasikan PostRepositoryInterface. Dengan cara ini, kami sedang tidak membatasi API (dan setiap implementasi lain) untuk interpretasi Ekiquent 's; kita dapat nama metode namun kami berharap.

Beberapa mungkin berpendapat bahwa Post model harus inject ke dalam implementasi ini untuk tujuan testability. Jika Anda setuju, hanya inject melalui konstruktor, per biasa.

Itu saja yang diperlukan! Refresh browser, dan hal-hal yang harus kembali ke normal. Hanya sekarang, aplikasi Anda jauh lebih baik terstruktur, dan controller tidak lagi terhubung dengan Eloquent.

Mari kita bayangkan bahwa, beberapa bulan dari sekarang, bos Anda memberitahu Anda bahwa Anda perlu untuk menukar Eloquent dengan Redis. Nah, karena Anda telah terstruktur aplikasi Anda di masa depan-bukti dengan cara ini, Anda hanya perlu membuat implementasi app/repositories/RedisPostRepository baru:

Dan update binding:

Seketika, Anda sekarang sedang memanfaatkan Redis dalam controller. Perhatikan bagaimana app/controllers/PostsController.php tidak pernah menyentuh? Itulah keindahan itu!


Struktur

Dalam pelajaran ini, organisasi kami sejauh ini agak kurang. IoC binding dalam routes.php file? Semua repositori dikelompokkan bersama dalam satu direktori? Tentu, itu mungkin bekerja di awal, tapi, sangat cepat, itu akan menjadi jelas bahwa ini tidak skala.

Di bagian akhir artikel ini, kita akan PSR-ify kode kita, dan memanfaatkan service provider untuk mendaftar setiap binding yang berlaku.

PSR-0 mendefinisikan persyaratan wajib yang harus ditaati untuk autoloader interoperabilitas.

Sebuah loader PSR-0 dapat didaftarkan dengan composer, melalui objek psr-0.

Sintaks dapat membingungkan pada awalnya. Jelas itu bagi saya. Cara mudah untuk menguraikan "Way": "app/lib/" adalah berpikir untuk diri sendiri, "folder dasar Way namespace terletak di app/lib." Tentu saja, mengganti nama terakhir saya dengan nama proyek Anda. Struktur direktori untuk pertandingan ini akan menjadi:

  • app/
    • lib/
    • Way/

Selanjutnya, daripada pengelompokan semua repositori ke dalam direktori repositories, pendekatan yang lebih elegan mungkin untuk mengkategorikan mereka ke dalam beberapa direktori, seperti:

  • app/
    • lib/
    • Way/
      • Storage/
      • Post/
        • PostRepositoryInterface.php
        • EloquentPostRepository.php

Sangat penting bahwa kami mematuhi konvensi penamaan dan folder ini, jika kita ingin autoloading untuk bekerja seperti yang diharapkan. Satu-satunya hal tersisa untuk dilakukan adalah memperbarui namespaces untuk PostRepositoryInterface dan EloquentPostRepository.

Dan untuk implementasi:

Sana kami pergi; itu jauh lebih bersih. Tapi bagaimana dengan binding? Rute file mungkin merupakan tempat yang nyaman untuk bereksperimen, tetapi itu tidak masuk akal untuk menyimpan mereka tidak secara permanen. Sebaliknya, kita akan menggunakan service provider.

Service provider yang tidak lebih dari bootstrap kelas yang dapat digunakan untuk melakukan apa pun yang Anda inginkan: mendaftar mengikat, menghubungkan ke dalam sebuah event, impor file route, dll.

register() Service Provider akan dipicu secara otomatis oleh Laravel.

Untuk membuat file ini dikenal untuk Laravel, Anda hanya perlu untuk memasukkan dalam app/config/app.php, dalam array providers.

Baik; Sekarang kita memiliki sebuah file yang didedikasikan untuk mendaftar binding baru.

Memperbarui tes

Dengan struktur baru kami di tempat, daripada mocking model Eloquent, itu sendiri, kami dapat sebaliknya mocking PostRepositoryInterface. Berikut adalah contoh dari satu tes tersebut:

Namun, kita dapat memperbaiki ini. Masuk akal bahwa setiap metode dalam PostsControllerTest akan memerlukan versi mock dari repositori. Dengan demikian, lebih baik untuk mengekstrak beberapa karya persiapan ini menjadi metode sendiri, seperti:

Tidak buruk, ay?

Sekarang, jika Anda ingin menjadi super-fly, dan bersedia untuk menambahkan sentuhan tes logika kode produksi, Anda bahkan bisa melakukan mocking dalam model Eloquent! Hal ini akan memungkinkan untuk:

Di belakang layar, ini akan mengejek PostRepositoryInterface, dan memperbarui binding IoC. Anda tidak mendapatkan jauh lebih mudah dibaca dari itu!

Memungkinkan untuk sintaks ini hanya membutuhkan Anda untuk memperbarui Post model, atau, lebih baik, BaseModel bahwa semua model Elqouent extend itu. Berikut adalah contoh bekas:

Jika Anda dapat mengatur "Harus saya menjadi embedding tes logika ke dalam kode produksi" dalam pertempuran, Anda akan menemukan bahwa hal ini memungkinkan untuk tes secara signifikan lebih mudah dibaca.

Rasanya enak, bukan? Mudah-mudahan, artikel ini belum terlalu berlebihan. Kuncinya adalah untuk belajar bagaimana untuk mengatur repositori Anda sedemikian rupa untuk membuatnya semudah mungkin untuk mocking dan inject ke controller Anda. Sebagai hasil dari upaya itu, tes Anda akan kilat cepat!

Artikel ini adalah kutipan dari buku mendatang, Laravel Testing Decoded. Menantikan untuk rilis pada Mei 2013!

Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.