个性化阅读
专注于IT技术分析

Laravel API教程:如何构建和测试RESTful API

本文概述

随着移动开发和JavaScript框架的兴起, 使用RESTful API是在数据和客户端之间构建单个接口的最佳选择。

Laravel是一个考虑到PHP开发人员生产力而开发的PHP框架。该框架是由Taylor Otwell撰写和维护的, 非常有思想性, 并且通过优先考虑约定而不是配置来努力节省开发人员的时间。该框架还旨在与Web一起发展, 并且已经在Web开发领域中合并了一些新功能和思想, 例如作业队列, 即用型API身份验证, 实时通信等等。

Laravel API教程-构建RESTful Web服务

在本教程中, 我们将探讨如何使用Laravel和身份验证来构建和测试强大的API。我们将使用Laravel 5.4, 所有代码都可以在GitHub上参考。

RESTful API

首先, 我们需要了解到底什么是RESTful API。 REST代表REpresentational State Transfer, 它是应用程序之间网络通信的体系结构样式, 它依赖于无状态协议(通常为HTTP)进行交互。

HTTP动词代表动作

在RESTful API中, 我们将HTTP动词用作操作, 而端点是操作的资源。我们将使用HTTP动词的语义:

  • GET:检索资源
  • POST:创建资源
  • PUT:更新资源
  • 删除:删除资源
HTTP动词:GET,POST,PUT和DELETE是RESTful API中的操作

更新操作:PUT与POST

RESTful API争论不休, 关于是否最好用POST, PATCH或PUT更新, 或者create动作最好留给PUT动词, 有很多意见。在本文中, 我们将使用PUT进行更新操作, 因为根据HTTP RFC, PUT意味着在特定位置创建/更新资源。 PUT动词的另一个要求是幂等, 在这种情况下, 基本上意味着你可以发送该请求1、2或1000次, 结果将是相同的:数据库中的一个更新资源。

资源资源

资源将是操作的目标, 在我们的案例中是”文章和用户”, 它们有自己的端点:

  • /文章
  • /用户

在此laravel API教程中, 资源在我们的数据模型上将具有1:1的表示形式, 但这不是必需的。你可以在多个数据模型中表示资源(或者根本不在数据库中表示资源), 并且完全为用户建模。最后, 你将决定如何以适合你的应用程序的方式来构造资源和模型。

关于一致性的注意事项

使用诸如REST之类的约定的最大优势在于, 你的API将更易于使用和开发。一些端点非常简单, 因此, 与使用诸如GET / get_article?id_article = 12和POST / delete_article?number = 40之类的端点相比, 你的API将更易于使用和维护。过去, 我已经建立了如此糟糕的API, 但我仍然为此感到讨厌。

但是, 在某些情况下, 将很难映射到”创建/检索/更新/删除”架构。请记住, 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时,应该会看到Laravel示例页面

在浏览器上打开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'];
}

可以使用Eloquent的create()和update()方法批量分配$ fillable属性中的字段。你还可以使用$ 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, ]);
        }
    }
}

因此, 我们运行seed命令:

$ 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('srcmini');

        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

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', '[email protected]');
Route::get('articles/{id}', '[email protected]');
Route::post('articles', '[email protected]');
Route::put('articles/{id}', '[email protected]');
Route::delete('articles/{id}', '[email protected]');

我们可以通过使用隐式路由模型绑定来改进端点。这样, Laravel将在我们的方法中插入Article实例, 如果找不到则自动返回404。我们必须在路线文件和控制器上进行更改:

Route::get('articles', '[email protected]');
Route::get('articles/{article}', '[email protected]');
Route::post('articles', '[email protected]');
Route::put('articles/{article}', '[email protected]');
Route::delete('articles/{article}', '[email protected]');
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:创建对象。对存储操作有用。
  • 204:没有内容成功执行动作后, 没有任何内容可返回。
  • 206:部分内容。当你必须返回分页的资源列表时很有用。
  • 400:错误的请求。无法通过验证的请求的标准选项。
  • 401:未经授权。用户需要认证。
  • 403:禁止用户已通过身份验证, 但没有执行操作的权限。
  • 404:找不到。找不到资源时, Laravel将自动返回该值。
  • 500内部服务器错误。理想情况下, 你不会明确返回此值, 但是如果意外中断, 这就是你的用户将收到的内容。
  • 503服务不可用。很容易说明, 但另一个代码不会由应用程序显式返回。

发送正确的404响应

如果你尝试获取不存在的资源, 则会引发异常, 并且会收到整个堆栈跟踪, 如下所示:

NotFoundHttpException Stacktrace

我们可以通过编辑位于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开箱即用提供了身份验证, 但是我们仍然需要对其进行一些调整以返回所需的响应。

如果API是英文,这就是api身份验证对话的声音

控制器利用特征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\[email protected]);

在上面的部分中, 我们在User模型上使用了一种方法来生成令牌。这很有用, 因此我们只有一种生成令牌的方式。将以下方法添加到你的用户模型:

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

        return $this->api_token;
    }
}

就是这样。现在, 该用户已注册, 并且由于Laravel的验证和开箱即用的身份验证, 因此需要名称, 电子邮件, 密码和password_confirmation字段, 并且反馈会自动进行处理。在RegisterController内检出validateator()方法以查看规则的实现方式。

这是我们到达那个终点时得到的:

$ curl -X POST http://localhost:8000/api/register \
 -H "Accept: application/json" \
 -H "Content-Type: application/json" \
 -d '{"name": "John", "email": "[email protected]", "password": "srcmini123", "password_confirmation": "srcmini123"}'
{
    "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特征的登录方法可以重写以支持我们的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\[email protected]');

现在, 假设播种机已运行, 这是向该路由发送POST请求时得到的结果:

$ curl -X POST localhost:8000/api/login \
  -H "Accept: application/json" \
  -H "Content-type: application/json" \
  -d "{\"email\": \"[email protected]\", \"password\": \"srcmini\" }"
{
    "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或以授权形式在请求标头中作为承载令牌来实现:Bearer Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw。

注销

按照我们目前的策略, 如果令牌有误或丢失, 则用户应该收到未经身份验证的响应(我们将在下一部分中实现)。因此, 对于简单的注销端点, 我们将发送令牌, 令牌将在数据库中删除。

路线/api.php:

Route::post('logout', 'Auth\[email protected]');

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

我们得到这样的结果:

InvalidArgumentException Stacktrace

这是因为我们需要在Handler类上编辑当前未经身份验证的方法。当前版本仅在请求具有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', '[email protected]');
    Route::get('articles/{article}', '[email protected]');
    Route::post('articles', '[email protected]');
    Route::put('articles/{article}', '[email protected]');
    Route::delete('articles/{article}', '[email protected]');
});

这样, 我们不必为每个路由都设置中间件。现在并不能节省很多时间, 但是随着项目的发展, 它有助于使路线保持干燥。

测试我们的端点

Laravel开箱即用地包含了与PHPUnit的集成, 并且已经设置了phpunit.xml。该框架还为我们提供了一些帮助程序和额外的断言, 这使我们的生活更加轻松, 尤其是对于测试API。

你可以使用许多外部工具来测试API。但是, 在Laravel中进行测试是更好的选择-我们可以在保留对数据库的完全控制的同时, 获得测试API结构和结果的所有好处。例如, 对于列表端点, 我们可以运行几个工厂并断言响应包含这些资源。

首先, 我们需要调整一些设置以使用内存中的SQLite数据库。使用它可以使我们的测试快速运行, 但是要权衡的是某些迁移命令(例如约束)在该特定设置中将无法正常工作。我建议你在开始出现迁移错误时, 或者如果你希望使用更强大的测试集而不是性能更高的测试时, 在测试中不要使用SQLite。

我们还将在每次测试之前运行迁移。这种设置将使我们能够为每个测试构建数据库, 然后销毁它, 从而避免了测试之间的任何类型的依赖性。

在我们的config / database.php文件中, 我们需要将sqlite配置中的数据库字段设置为: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('srcmini123'), ]);

        $payload = ['email' => '[email protected]', 'password' => 'srcmini123'];

        $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' => 'srcmini123', 'password_confirmation' => 'srcmini123', ];

        $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' => 'srcmini123', ];

        $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实例中, 以避免再次点击数据库。但是, 明智的选择-在这种情况下, 这意味着我们必须将注销测试分成两个, 以避免先前缓存的用户遇到任何问题。

测试Article端点也很简单:

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设计的黄金规则。

赞(0)
未经允许不得转载:srcmini » Laravel API教程:如何构建和测试RESTful API

评论 抢沙发

评论前必须登录!