Пожалуйста, посетите оригиналLaravel API Tutorial: How to Build and Test a RESTful API.
С ростом развития мобильных приложений и фреймворков JavaScript использование API RESTful является лучшим вариантом для создания единого интерфейса между данными и клиентом.
Laravelпредставляет собой PHP-фреймворк и фокусируется наРазработчикпроизводительность. Написанная и поддерживаемая Тейлором Отвеллом, структура имеет большой смысл и стремится сэкономить время разработчиков с помощью соглашений, облегчающих настройку. Платформа также предназначена для развития вместе с Интернетом и включает в себя несколько новых функций и идей в мире веб-разработки, таких как очереди заданий, готовая аутентификация API, связь в реальном времени и многое другое.
В этой статье мы рассмотрим, как использовать Laravel для аутентификации, чтобы создать и протестировать надежный API. Мы будем использовать Laravel 5.4, весь код можно найти вГитхабСсылаться на.
RESTful API
Во-первых, нам нужно понять, что такое RESTful API. REST, что означает RE для передачи состояния, представляет собой архитектурный стиль для сетевого взаимодействия между приложениями, который использует для взаимодействия протокол без сохранения состояния (обычно HTTP).
Глаголы HTTP выражают действия
В RESTful API мы используем HTTP-глаголы в качестве действий, а конечные точки — это ресурсы, на которые воздействуют. Мы будем использовать семантику глаголов HTTP:
- ПОЛУЧИТЬ: получить ресурс
- POST: создать ресурс
- PUT: обновить ресурс
- УДАЛИТЬ: удалить ресурс
Операции обновления: PUT и POST
API-интерфейсы RESTful вызывают много споров, и существует много мнений о том, лучше ли использовать POST, PATCH или PUT для обновлений, или лучше оставить действие создания глаголу PUT. В этой статье мы будем использовать PUT для операций обновления, согласно HTTP RFC, PUT означает создание/обновление ресурса в определенном месте. Еще одним требованием к глаголу PUT является идемпотентность, что в данном случае означает, что вы можете отправить этот запрос 1, 2 или 1000 раз, и результат будет одинаковым: обновленный ресурс в базе данных.
ресурс
Объектом действия будет ресурс, в случае с нашими статьями и пользователями у них свои конечные точки:
- /articles
- /users
В этом руководстве по laravel API ресурсы будут иметь представление 1:1 в нашей модели данных, но это не является обязательным требованием. Вы можете представлять ресурсы в нескольких моделях данных (или вообще не представлять их в базе данных), и эти модели полностью независимы от пользователя. В конце концов, вы решите, как структурировать ресурсы и модели в соответствии с вашим приложением.
Примечания о согласованности
Самым большим преимуществом использования набора соглашений, таких как REST, является то, что ваш API будет проще использовать и разрабатывать. Некоторые конечные точки очень просты, поэтому ваш APIGET /get_article?id_article=12
а такжеPOST /delete_article?number=40
Конечные точки и т. д. проще в использовании и обслуживании. В прошлом я создавал ужасные API и до сих пор ненавижу себя.
Однако будет сложно сопоставить шаблоны создания/извлечения/обновления/удаления. Помните, что URL-адреса не должны содержать глаголов, а ресурсы не обязательно должны быть строками в таблице. Еще одна вещь, о которой следует помнить, это то, что вам не нужно реализовывать каждое действие для каждого ресурса.
Настройте свой проект
Как и во всех современных PHP-фреймворках, нам нужноComposerдля установки и обработки наших зависимостей. После того, как вы выполнили инструкции по загрузке (и добавили в переменную окружения PATH), установите Laravel с помощью следующей команды:
$ composer global require laravel/installer
После установки вы можете поддержать новое приложение:
$ laravel new myapp
Для приведенной выше команды вам нужно иметь ~/composer/vendor/bin в вашем $PATH. Если вы не хотите иметь с этим дело, вы также можете создать новый проект с помощью 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, коробку Vagrant, специально созданную для Laravel, но это выходит за рамки этой статьи. Если вы хотите узнать больше,См. документацию по усадьбе.
Начнем с нашей первой модели и миграции — статьи. Статья должна иметь название и поле body, а также дату создания. Laravel предоставляет несколько команд через инструмент командной строки Artisan-Laravel, которые могут помочь нам, создавая файлы и помещая их в правильные папки. Чтобы создать модель статьи, мы можем запустить:
$ 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() будет создана намив и обновляется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 поставляется из коробки с двумя миграциями, создайтеusersтаблицу и создатьpasswordresetsстол . мы не используем парольсбрасывает таблицу, но было бы полезно иметь для нас готовую таблицу пользователей.
Теперь вернемся к нашей модели и добавим эти свойства в поле $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,
]);
}
}
}
Итак, давайте запустим команду 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('toptal');
User::create([
'name' => 'Administrator',
'email' => 'admin@test.com',
'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,
]);
}
}
}
Мы можем сделать это, добавив семена в основной класс DatabaseSeeder в папке database/seeds:
class DatabaseSeeder extends Seeder
{
public function run()
{
$this->call(ArticlesTableSeeder::class);
$this->call(UsersTableSeeder::class);
}
}
Таким образом, мы можем просто запустить $ php artisan db:seed, и он запустит все вызываемые классы в методе run().
Маршрутизация и контроллер
Мы создаем основные конечные точки для нашего приложения: создание, получение списка, получение одного, обновление и удаление. В файле route/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 будет автоматически применяться к этим маршрутам (вы можете отредактировать класс RouteServiceProvider в RouteServiceProvider, если хотите удалить префикс).
Теперь давайте переместим этот код в наш собственный контроллер:
$ 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;
}
}
файл маршрутов/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 внедрит экземпляр статьи в наш метод и автоматически вернет 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 : Объект создан. Применяется к поведению магазина.
- 204: Нет содержимого. Когда действие выполняется успешно, но ничего не возвращает.
- 206: частичное содержание. Полезно, когда вам нужно вернуть список ресурсов с разбивкой на страницы.
- 400 : Неверный запрос Стандартный вариант для запросов, не прошедших проверку.
- 401: Неавторизованный пользователь должен пройти аутентификацию.
- 403: Forbidden Пользователь аутентифицирован, но не имеет разрешения на выполнение операции.
- 404: Not Found Это будет автоматически возвращено Laravel, если ресурс не найден.
- внутренняя ошибка сервера 500. В идеале вы не должны возвращать это сообщение явно, но в случае непредвиденного сбоя это то, что получат ваши пользователи.
- 503: Служба недоступна Довольно очевидный код, который явно не возвращается приложением.
Отправить правильный ответ 404
Если вы попытаетесь получить несуществующий ресурс, будет сгенерировано исключение, и вы получите всю трассировку стека, например:
Мы можем сделать это, изменив наш класс обработчика исключений (расположенный в app/Exceptions/Handler.php) на 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 .
Сертификация
Существует множество способов реализовать аутентификацию API в Laravel (один из них —Passport, отличный способ реализовать OAuth2), но в этой статье мы воспользуемся очень упрощенным подходом.
Для начала нам нужно добавить поле 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());
}
Нам просто нужно реализовать метод Registered() в RegisterController. Метод получает $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 и готовой проверки поля имени, электронной почты, пароля и пароля_подтверждения являются обязательными, а обратная связь обрабатывается автоматически. Ознакомьтесь с методом validator() в RegisterController, чтобы увидеть, как реализуются правила.
Когда мы достигнем этой конечной точки, мы получим следующее:
$ curl -X POST http://localhost:8000/api/register \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "John", "email": "john.doe@toptal.com", "password": "toptal123", "password_confirmation": "toptal123"}'
{
"data": {
"api_token":"0syHnl0Y9jOIfszq11EC2CBQwCfObmvscrZYo5o2ilZPnohvndH797nDNyAT",
"created_at": "2017-06-20 21:17:15",
"email": "john.doe@toptal.com",
"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\LoginController@login');
Теперь, предполагая, что сидер запущен, вот что мы получаем, когда отправляем POST-запрос на этот маршрут:
$ curl -X POST localhost:8000/api/login \
-H "Accept: application/json" \
-H "Content-type: application/json" \
-d "{\"email\": \"admin@test.com\", \"password\": \"toptal\" }"
{
"data": {
"id":1,
"name":"Administrator",
"email":"admin@test.com",
"created_at":"2017-04-25 01:05:34",
"updated_at":"2017-04-25 02:50:40",
"api_token":"Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw"
}
}
Чтобы отправить токен в запросе, вы можете отправить атрибут api_token в полезной нагрузке или в качестве токена носителя в заголовке запроса в форме Authorization: Bearer Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw.
выйти
С нашей текущей стратегией, если токен неверный или отсутствует, пользователь должен получить неаутентифицированный ответ (который мы реализуем в следующем разделе). Таким образом, для простой конечной точки выхода мы отправим токен, и он будет удален из базы данных.
маршруты/api.php :
Route::post('logout', 'Auth\LoginController@logout');
Аут\ЛогинКонтроллер.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::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. Текущая версия возвращает JSON только в том случае, если запрос имеет заголовок Accept: application/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 на :memory: :
...
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
...
]
Затем включите SQLite в phpunit.xml, добавив переменную среды 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>
После всего этого остается только настроить наш базовый класс TestCase для использования миграций и заполнения базы данных перед каждым тестом. Для этого нам нужно добавить трейт DatabaseMigrations, а затем добавить вызов Artisan в наш метод setUp(). Вот измененный класс:
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');
}
}
Мне больше всего нравится, это добавить команду test для 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Библиотеки были внедрены, чтобы помочь нам создать правильный формат случайных данных для наших моделей.
Наш первый тест
Мы можем использовать метод assert Laravel, чтобы легко попасть в конечную точку и оценить ее ответ. Мы создаем наш первый тест, тест входа в систему, с помощью следующей команды:
$ 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' => 'testlogin@user.com',
'password' => bcrypt('toptal123'),
]);
$payload = ['email' => 'testlogin@user.com', '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' => 'john@toptal.com',
'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' => 'john@toptal.com',
'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' => 'user@test.com']);
$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' => 'user@test.com']);
$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, чтобы снова избежать базы данных 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), но я хочу сделать основы создания и тестирования API в Laravel во внешней обертке.
Laravel определенно улучшил мой опыт работы с PHP, а простота тестирования укрепила мой интерес к фреймворку. Это не идеально, но достаточно гибко, чтобы вы могли решать проблемы.
Если вы разрабатываете общедоступный API, см. "Золотые правила дизайна веб-API" .