Laravel API 教程:如何構建和測試 RESTful API
已發表: 2022-03-11隨著移動開發和 JavaScript 框架的興起,使用 RESTful API 是在數據和客戶端之間構建單一接口的最佳選擇。
Laravel 是一個 PHP 框架,開發時考慮了 PHP 開發人員的生產力。 該框架由 Taylor Otwell 編寫和維護,非常固執己見,並通過支持約定而不是配置來努力節省開發人員的時間。 該框架還旨在與 Web 一起發展,並且已經在 Web 開發世界中融入了一些新功能和想法——例如作業隊列、開箱即用的 API 身份驗證、實時通信等等。
在本教程中,我們將探索使用 Laravel 和身份驗證構建和測試強大 API 的方法。 我們將使用 Laravel 5.4,所有代碼都可以在 GitHub 上參考。
RESTful API
首先,我們需要了解究竟什麼是 RESTful API。 REST 代表REpresentational State Transfer ,是一種應用程序之間網絡通信的架構風格,它依賴於無狀態協議(通常是 HTTP)進行交互。
HTTP 動詞表示動作
在 RESTful API 中,我們使用 HTTP 動詞作為操作,端點是所作用的資源。 我們將使用 HTTP 動詞的語義含義:
-
GET
: 檢索資源 POST
: 創建資源PUT
:更新資源DELETE
: 刪除資源
更新操作:PUT 與 POST
RESTful API 是一個有很多爭論的問題,關於是否最好使用POST
、 PATCH
或PUT
進行更新,或者是否最好將創建操作留給PUT
動詞,有很多意見。 在本文中,我們將使用PUT
進行更新操作,根據 HTTP RFC, PUT
意味著在特定位置創建/更新資源。 PUT
動詞的另一個要求是冪等性,在這種情況下,這基本上意味著您可以發送該請求 1、2 或 1000 次,結果將是相同的:數據庫中的一個更新資源。
資源
資源將是操作的目標,在我們的例子中是文章和用戶,它們有自己的端點:
-
/articles
-
/users
在這個 laravel api 教程中,資源將在我們的數據模型上以 1:1 表示,但這不是必需的。 您可以在多個數據模型中表示資源(或在數據庫中根本不表示),並且模型完全不受用戶限制。 最後,您將決定如何以適合您的應用程序的方式構建資源和模型。
關於一致性的說明
使用一組約定(如 REST)的最大優勢是您的 API 將更容易使用和開發。 一些端點非常簡單,因此,與使用GET /get_article?id_article=12
和POST /delete_article?number=40
等端點相比,您的 API 將更易於使用和維護。 我過去曾構建過這樣糟糕的 API,但我仍然為此痛恨自己。
但是,在某些情況下,很難映射到 Create/Retrieve/Update/Delete 模式。 請記住,URL 不應包含動詞,並且資源不一定是表中的行。 要記住的另一件事是,您不必為每個資源實施每個操作。
設置 Laravel Web 服務項目
與所有現代 PHP 框架一樣,我們需要 Composer 來安裝和處理我們的依賴項。 按照下載說明(並添加到路徑環境變量)後,使用以下命令安裝 Laravel:
$ composer global require laravel/installer
安裝完成後,您可以像這樣構建一個新應用程序:
$ laravel new myapp
對於上述命令,您需要在$PATH
中有~/composer/vendor/bin
。 如果你不想處理這個問題,你也可以使用 Composer 創建一個新項目:
$ composer create-project --prefer-dist laravel/laravel myapp
安裝 Laravel 後,您應該能夠啟動服務器並測試一切是否正常:
$ php artisan serve Laravel development server started: <http://127.0.0.1:8000>
localhost:8000
時,您應該會看到此示例頁面。遷移和模型
在實際編寫您的第一個遷移之前,請確保您已為此應用程序創建了一個數據庫,並將其憑據添加到位於項目根目錄中的.env
文件中。
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret
你也可以使用 Homestead,一個專為 Laravel 設計的 Vagrant 盒子,但這有點超出了本文的範圍。 如果您想了解更多信息,請參閱 Homestead 文檔。
讓我們開始我們的第一個模型和遷移——文章。 文章應該有一個標題和一個正文字段,以及一個創建日期。 Laravel 通過 Artisan(Laravel 的命令行工具)提供了幾個命令,它們通過生成文件並將它們放在正確的文件夾中來幫助我們。 要創建 Article 模型,我們可以運行:
$ php artisan make:model Article -m
-m
選項是--migration
的縮寫,它告訴 Artisan 為我們的模型創建一個。 這是生成的遷移:
<?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'); } }
讓我們仔細分析一下:
-
up()
和down()
方法將分別在我們遷移和回滾時運行; -
$table->increments('id')
設置一個名為id
的自動遞增整數; -
$table->timestamps()
將為我們設置時間戳——created_at
和updated_at
,但不要擔心設置默認值,Laravel 會在需要時更新這些字段。 - 最後,如果表存在,
Schema::dropIfExists()
當然會刪除表。
順便說一句,讓我們up()
方法中添加兩行:
public function up() { Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->text('body'); $table->timestamps(); }); }
string()
方法創建一個VARCHAR
等效列,而text()
創建一個TEXT
等效列。 完成後,讓我們繼續遷移:
$ php artisan migrate
您還可以在此處使用
--step
選項,它將每個遷移分成自己的批次,以便您可以在需要時單獨回滾它們。
Laravel 開箱即用帶有兩個遷移, create_users_table
和create_password_resets_table
。 我們不會使用password_resets
表,但是為我們準備好users
表會很有幫助。
現在讓我們回到我們的模型並將這些屬性添加到$fillable
字段,以便我們可以在Article::create
和Article::update
模型中使用它們:
class Article extends Model { protected $fillable = ['title', 'body']; }
$fillable
屬性中的字段可以使用 Eloquent 的create()
和update()
方法進行批量分配。 您還可以使用$guarded
屬性來允許除少數屬性之外的所有屬性。
數據庫播種
數據庫播種是用我們可以用來測試它的虛擬數據填充我們的數據庫的過程。 Laravel 帶有 Faker,這是一個很棒的庫,可以為我們生成正確格式的虛擬數據。 所以讓我們創建我們的第一個播種機:
$ php artisan make:seeder ArticlesTableSeeder
播種機將位於/database/seeds
目錄中。 以下是我們設置它以創建幾篇文章後的樣子:
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, ]); } } }
所以讓我們運行種子命令:
$ php artisan db:seed --class=ArticlesTableSeeder
讓我們重複創建用戶播種器的過程:
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, ]); } } }
我們可以通過將播種器添加到database/seeds
文件夾中的主DatabaseSeeder
類來使其更容易:
class DatabaseSeeder extends Seeder { public function run() { $this->call(ArticlesTableSeeder::class); $this->call(UsersTableSeeder::class); } }
這樣,我們可以簡單地運行$ php artisan db:seed
,它將運行run()
方法中所有被調用的類。
路由和控制器
讓我們為我們的應用程序創建基本端點:創建、檢索列表、檢索單個、更新和刪除。 在routes/api.php
文件中,我們可以簡單地這樣做:
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; })
api.php
中的路由將以/api/
為前綴,API 限制中間件將自動應用於這些路由(如果要刪除前綴,可以編輯/app/Providers/RouteServiceProvider.php
上的RouteServiceProvider
類)。
現在讓我們把這段代碼移到它自己的 Controller 中:
$ php artisan make:controller 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; } }
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');
我們可以通過使用隱式路由模型綁定來改進端點。 這樣,Laravel 會在我們的方法中註入Article
實例,如果沒有找到它會自動返回 404。 我們必須對路由文件和控制器進行更改:
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); } }
關於 HTTP 狀態碼和響應格式的說明
我們還向端點添加了response()->json()
調用。 這讓我們可以顯式返回 JSON 數據,並發送客戶端可以解析的 HTTP 代碼。 您將返回的最常見代碼是:
-
200
:好的。 標準成功代碼和默認選項。 -
201
:對像已創建。 對store
操作很有用。 -
204
:沒有內容。 當一個動作執行成功,但沒有返回內容。 -
206
:部分內容。 當您必須返回分頁的資源列表時很有用。 -
400
:錯誤請求。 未能通過驗證的請求的標準選項。 -
401
:未經授權。 用戶需要經過身份驗證。 -
403
:禁止。 用戶已通過身份驗證,但沒有執行操作的權限。 -
404
:未找到。 當找不到資源時,Laravel 會自動返回。 -
500
:內部服務器錯誤。 理想情況下,您不會明確返回此內容,但如果出現意外中斷,這就是您的用戶將收到的內容。 -
503
:服務不可用。 非常不言自明,但還有另一個代碼不會被應用程序顯式返回。
發送正確的 404 響應
如果你試圖獲取一個不存在的資源,你會被拋出一個異常,你會收到整個堆棧跟踪,像這樣:
我們可以通過編輯位於app/Exceptions/Handler.php
中的異常處理程序類來解決這個問題,以返回 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); }
這是一個返回的例子:
{ data: "Resource not found" }
如果您使用 Laravel 服務其他頁面,則必須編輯代碼以使用Accept
標頭,否則來自常規請求的 404 錯誤也會返回 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); }
在這種情況下,API 請求將需要標頭Accept: application/json
。
驗證
在 Laravel 中實現 API 身份驗證的方法有很多(其中之一是 Passport,這是實現 OAuth2 的好方法),但在本文中,我們將採用一種非常簡化的方法。
首先,我們需要在users
表中添加一個api_token
字段:
$ php artisan make:migration --table=users adds_api_token_to_users_table
然後實施遷移:
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']); }); }
之後,只需使用以下命令運行遷移:
$ php artisan migrate
創建註冊端點
我們將使用RegisterController
(在Auth
文件夾中)在註冊時返回正確的響應。 Laravel 提供了開箱即用的身份驗證,但我們仍然需要對其進行一些調整以返回我們想要的響應。
控制器使用特徵RegistersUsers
來實現註冊。 以下是它的工作原理:
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()); }
我們只需要在RegisterController
中實現registered()
方法。 該方法接收$request
和$user
,這就是我們想要的。 以下是該方法在控制器內部的外觀:
protected function registered(Request $request, $user) { $user->generateToken(); return response()->json(['data' => $user->toArray()], 201); }
我們可以將它鏈接到路由文件中:

Route::post('register', 'Auth\RegisterController@register');
在上面的部分中,我們使用了 User 模型上的方法來生成令牌。 這很有用,因此我們只有一種生成令牌的方法。 將以下方法添加到您的用戶模型中:
class User extends Authenticatable { ... public function generateToken() { $this->api_token = str_random(60); $this->save(); return $this->api_token; } }
就是這樣。 用戶現在已經註冊,並且由於 Laravel 的驗證和開箱即用的身份驗證, name
、 email
、 password
和password_confirmation
字段是必需的,並且反饋會自動處理。 檢查RegisterController
中的validator()
方法以查看規則是如何實現的。
這是我們到達那個端點時得到的結果:
$ 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" } }
創建登錄端點
就像註冊端點一樣,我們可以編輯LoginController
(在Auth
文件夾中)以支持我們的 API 身份驗證。 AuthenticatesUsers
trait 的login
方法可以被覆蓋以支持我們的 API:
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); }
我們可以將它鏈接到路由文件中:
Route::post('login', 'Auth\LoginController@login');
現在,假設播種機已經運行,當我們向該路由發送POST
請求時,我們會得到以下結果:
$ 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" } }
要在請求中發送令牌,您可以通過在有效負載中發送屬性api_token
或作為Authorization: Bearer Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw
。
註銷
使用我們當前的策略,如果令牌錯誤或丟失,用戶應該會收到未經身份驗證的響應(我們將在下一節中實現)。 因此,對於一個簡單的註銷端點,我們將發送令牌,並將其從數據庫中刪除。
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); }
使用此策略,用戶擁有的任何令牌都將無效,並且 API 將拒絕訪問(使用中間件,如下一節所述)。 這需要與前端協調,以避免用戶在無法訪問任何內容的情況下保持登錄狀態。
使用中間件限制訪問
創建api_token
,我們可以在路由文件中切換身份驗證中間件:
Route::middleware('auth:api') ->get('/user', function (Request $request) { return $request->user(); });
我們可以使用$request->user()
方法或通過 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
我們得到這樣的結果:
這是因為我們需要在 Handler 類上編輯當前unauthenticated
身份驗證的方法。 當前版本僅在請求具有Accept: application/json
標頭時才返回 JSON,因此讓我們對其進行更改:
protected function unauthenticated($request, AuthenticationException $exception) { return response()->json(['error' => 'Unauthenticated'], 401); }
修復後,我們可以返回文章端點將它們包裝在auth:api
中間件中。 我們可以通過使用路由組來做到這一點:
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'); });
這樣我們就不必為每個路由設置中間件。 它現在並沒有節省很多時間,但隨著項目的發展,它有助於保持路線乾燥。
測試我們的端點
Laravel 包含與 PHPUnit 的開箱即用集成,並且已經設置了phpunit.xml
。 該框架還為我們提供了一些幫助程序和額外的斷言,使我們的生活變得更加輕鬆,尤其是在測試 API 時。
您可以使用許多外部工具來測試您的 API; 然而,在 Laravel 中進行測試是一個更好的選擇——我們可以獲得測試 API 結構和結果的所有好處,同時保持對數據庫的完全控制。 例如,對於列表端點,我們可以運行幾個工廠並斷言響應包含這些資源。
首先,我們需要調整一些設置以使用內存中的 SQLite 數據庫。 使用它將使我們的測試運行得很快,但代價是某些遷移命令(例如約束)在該特定設置中無法正常工作。 當您開始遇到遷移錯誤或者您更喜歡一組更強大的測試而不是高性能運行時,我建議您在測試中遠離 SQLite。
我們還將在每次測試之前運行遷移。 這種設置將允許我們為每個測試構建數據庫然後銷毀它,避免測試之間的任何類型的依賴關係。
在我們的config/database.php
文件中,我們需要將sqlite
配置中的database
字段設置為:memory:
:
... 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], ... ]
然後通過添加環境變量DB_CONNECTION
在phpunit.xml
中啟用 SQLite:
<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>
有了這些,剩下的就是配置我們的基本TestCase
類以使用遷移並在每次測試之前為數據庫播種。 為此,我們需要添加DatabaseMigrations
特徵,然後在我們的setUp()
方法上添加Artisan
調用。 這是更改後的課程:
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'); } }
我喜歡做的最後一件事是將測試命令添加到composer.json
:
"scripts": { "test" : [ "vendor/bin/phpunit" ], ... },
測試命令將像這樣可用:
$ composer test
為我們的測試設置工廠
工廠將使我們能夠快速創建具有正確數據的對像以進行測試。 它們位於database/factories
文件夾中。 Laravel 開箱即用,為User
類提供了一個工廠,所以讓我們為Article
類添加一個工廠:
$factory->define(App\Article::class, function (Faker\Generator $faker) { return [ 'title' => $faker->sentence, 'body' => $faker->paragraph, ]; });
Faker 庫已經被注入來幫助我們為我們的模型創建正確格式的隨機數據。
我們的第一次測試
我們可以使用 Laravel 的 assert 方法輕鬆命中端點並評估其響應。 讓我們使用以下命令創建我們的第一個測試,即登錄測試:
$ php artisan make:test Feature/LoginTest
這是我們的測試:
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', ], ]); } }
這些方法測試了幾個簡單的案例。 json()
方法到達端點,其他斷言非常不言自明。 關於assertJson()
的一個細節:此方法將響應轉換為數組搜索參數,因此順序很重要。 在這種情況下,您可以鏈接多個assertJson()
調用。
現在,讓我們創建註冊端點測試並為該端點編寫一對:
$ 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.'], ]); } }
最後,註銷端點:
$ 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); } }
需要注意的是,在測試期間,Laravel 應用程序不會在新請求上再次實例化。 這意味著當我們點擊身份驗證中間件時,它將當前用戶保存在
TokenGuard
實例中,以避免再次點擊數據庫。 然而,一個明智的選擇——在這種情況下,這意味著我們必須將註銷測試分成兩部分,以避免之前緩存的用戶出現任何問題。
測試文章端點也很簡單:
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'], ]); } }
下一步
這裡的所有都是它的。 肯定有改進的空間——你可以使用 Passport 包實現 OAuth2,集成分頁和轉換層(我推薦 Fractal),不勝枚舉——但我想了解在 Laravel 中創建和測試 API 的基礎知識外部包。
Laravel 開發無疑改善了我使用 PHP 的體驗,並且使用它進行測試的便利性鞏固了我對該框架的興趣。 它並不完美,但它足夠靈活,可以讓您解決它的問題。
如果您正在設計公共 API,請查看 5 條優秀 Web API 設計的黃金法則。