Создаем RESTful API в Laravel 5: как написать и протестировать

  • DeveloperNotes
  • »
  • Laravel 5
  • »
  • Создаем RESTful API в Laravel 5: как написать и протестировать

С ростом количества JavaScript-фреймворков создание своего RESTful API - лучший выбор для построения единого интерфейса между сервером и приложением. А также API может помочь при взаимодействии вашего сайта со сторонними сервисами.

Laravel 5 идеально подходит для выполнения этой задачи. Этот PHP-фреймворк, написанный Тайлером Отвеллом очень гибок и позволяет PHP-разработчику сохранить много времени в процессе разработки. Кроме этого, он поддерживает множество разнообразных новых идей, которые были воплощены в жизнь: очереди, авторизация в API, события в реальном времени и многое другое.

В этом руководстве мы разберемся, как написать RESTful API с использованием Laravel 5. Оно будет включать в себя авторизацию и тестирование. Весь код вы можете найти на GitHub.

Что такое RESTful API?

Первое, что надо сделать - разобраться, а что такое RESTful API? Слово REST здесь означает "REpresentational State Transfer" или по-русски "передача состояния представления". Это архитектурное решение для коммуникации приложений через компьютерную сеть, которое базируется на протоколе без состояния (в нашем случае - HTTP).

В RESTful API действием является тип HTTP-запроса. А URL запроса - это так называемый endpoint, точка запроса ресурса. Мы будем придерживаться следующей договоренности по поводу типов запросов:

  • GET: получение данных
  • POST: создание данных
  • PUT: обновление данных
  • DELETE: удаление данных

До сих пор ведутся дебаты о том, что лучше для обновления данных: PUT, POST или PATCH. В этой статье мы будем использовать PUT, чтобы обновить данные. Второе требование, выдвигаемое к PUT - идемпотентность. Говоря проще: не важно, сколько раз вы сделали запрос через API 1, 2 или 1000, результат должен быть один и тот же: одно обновление ресурса в базе данных.

Установим новое приложение на Laravel

Для начала работы нам понадобится, разумеется, сам Laravel. Установим его через Composer, выполнив следующую команду в терминале:

$ composer global require laravel/installer

После завершения установки, вы можете установить laravel вот так:

$ laravel new myapp

Чтобы эта команда сработала, каталог ~/composer/vendor/bin должен находиться в переменной окружения $PATH. Есть и другой способ установки нового проекта:

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 #и тут

Теперь нам необходимо создать модель и миграцию. Подробнее об этом я писал в статье Модели в Laravel 5, прошу ознакомиться, если вы не знаете, что это такое. Выполните следующую команду в терминале:

$ php artisan make:model Article -m

Опция -m говорит artisan, что нужно создать не только модель, но и миграцию для нее.

Вот код миграции, которую нам сгенерировал Laravel:

<?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 как auto_increment.
  • $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();
    });
}

Теперь самое время применить нашу миграцию, чтобы создать таблицу в базе данных. Для этого выполните команду:

php artisan migrate

Теперь давайте вернемся к нашей модели и добавим эти атрибуты в поле $fillable, чтобы мы могли использовать их в методах Article::create и Article::update:

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

Сидер (seed) для базы данных

Давайте наполним нашу базу данных тестовыми данными, чтобы не забивать их вручную. Laravel поставляется с прекрасным пакетом под названием Faker. Он поможет нам. Для начала создадим новый seed:

$ php artisan make:seeder ArticlesTableSeeder

Все сидеры будут находиться в директории /database/seeds. Напишем наш новый сидер:

class ArticlesTableSeeder extends Seeder
{
    public function run()
    {
        // Удалим имеющиеся в таблице данные
        Article::truncate();

        $faker = \Faker\Factory::create();

        // А теперь давайте создадим 50 статей в нашей таблице
        for ($i = 0; $i < 50; $i++) {
            Article::create([
                'title' => $faker->sentence,
                'body' => $faker->paragraph,
            ]);
        }
    }
}

Чтобы сидер сработал, необходимо запустить команду:

$ php artisan db:seed --class=ArticlesTableSeeder

Давайте повторим то же самое для таблицы users, которая поставляется вместе с нашим Laravel'ом:

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

Роуты и контроллеры для RESTful API в Laravel 5

Давайте создадим наши основные точки доступа (endpoint / endpoints) к нашему API. Добавьте следующий код в файл 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/ и middleware, предотвращающий спам, автоматически будет применен ко всем запросам сюда. Вы можете изменить это поведение, отредактировав RouteServiceProvider в /app/Providers/RouteServiceProvider.php.

То, что мы сделали выше - хороший образец, что код можно писать прямо в api.php. Но давайте все же перенесем код в специально созданный для него контроллер:

$ 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', '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-статусы и формат ответа для RESTful в Laravel 5

Мы также добавим вызов response()->json() для всех наших эндпоинтов, чтобы Laravel автоматически возвращал данные в формате JSON. А самые популярные коды HTTP-ответов в REST следующие:

  • 200: OK. Ответ сервера при успешном обращении, а также - стандартный ответ.
  • 201: Object created. Полезно при операцих сохранения и создания.
  • 204: No content. Когда действие было выполнено, но нет контента, который можно вернуть.
  • 206: Partial content. Полезно, когда вам нужно вернуть контент, разбитый постранично.
  • 400: Bad request. Стандартный ответ, когда запросу не удалось пройти валидацию (он неправильно составлен).
  • 401: Unauthorized. Запрашивающий не авторизован.
  • 403: Forbidden. Пользователь авторизован, но не имеет доступа к запрашиваему ресурсу.
  • 404: Not found. Ресурс не был найден.
  • 500: Internal server error. Возвращается, когда что-то пошло не так и отвечающее (наше) приложение завершилось неожиданно (обычно - с ошибкой).
  • 503: Service unavailable. Сервис в данный момент недоступен (например, перегружен).

Отсылка корректного 404-ого ответа

Если вы попробуете запросить несуществующий ресурс, то вместо 404-ого ответа получите полный стек-трейс и сообщение об ошибке. Давайте исправим это, отредактировав файл app/Exceptions/Handler.php так, чтобы он отсылал корректный JSON:

public function render($request, Exception $exception)
{
    if ($exception instanceof ModelNotFoundException) {
        return response()->json([
            'error' => 'Ничего не найдено'
        ], 404);
    }

    return parent::render($request, $exception);
}

Вот пример такого ответа:

{
    data: "Ничего не найдено"
}

Авторизация в RESTful API для Laravel 5

В Laravel существует много способов создать авторизацию для RESTful API. Один из отличных способов - использовать Passport, который позволит вам авторизовывать пользователей по протоколу OAuth2. Но в этой статье мы выберем подход попроще.

Чтобы начать, нам нужно поле api_token в таблице пользователей:

Для его добавления давайте создадим миграцию:

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

Регистрация в RESTful API для Laravel 5

Давайте используем RegisterController (находящийся в каталоге Auth), чтобы разработать регистрацию. Хотя Laravel и поставляет из коробки механизм регистраций, нам придется его немного доработать.

Этот контроллер использует трейт RegistersUsers, чтобы организовать регистрацию. Вот как он работает:

public function register(Request $request)
{
    $this->validator($request->all())->validate();

    event(new Registered($user = $this->create($request->all())));

    $this->guard()->login($user);

    return $this->registered($request, $user)
                    ?: redirect($this->redirectPath());
}

Мы собираемся переопределить метод register() с тем, чтобы он выполнял нужную для нас работу:

protected function registered(Request $request, $user)
{
    $user->generateToken();

    return response()->json(['data' => $user->toArray()], 201);
}

Также нам необходимо добавить ссылку на него в роутах:

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

Теперь нам понадобится метод, позволяющий сгенерировать токен пользователя и записать его в поле api_token в таблице пользователей. Добавьте следующий метод в модель User:

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

        return $this->api_token;
    }
}

Вот каким образом мы можем попробовать зарегистрировать нового пользователя в нашем API. Используем CURL:

$ curl -X POST http://localhost:8000/api/register \
 -H "Accept: application/json" \
 -H "Content-Type: application/json" \
 -d '{"name": "MyName", "email": "name@mail.com", "password": "123123", "password_confirmation": "123123"}'

В ответ мы должны получить:

{
    "data": {
        "api_token":"0syHnl0Y9jOIfszq11EC2CBQwCfObmvscrZY95o2ilZPnohvndH797nDNyAT",
        "created_at": "2019-01-20 21:17:15",
        "email": "john.doe@toptal.com",
        "id": 3,
        "name": "MyName",
        "updated_at": "2019-01-20 21:17:15"
    }
}

Создание логина (входа) в RESTful API для Laravel 5

Прямо как с регистрацией, мы будем использовать LoginController (находящийся в каталоге Auth), чтобы обрабатывать попытку входа пользователей. В данном случае мы должны переопределить метод login(), чтобы всё работало:

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');

Теперь протестируем возможность логина пользователя используя CURL, как и раньше:

$ curl -X POST localhost:8000/api/login \
  -H "Accept: application/json" \
  -H "Content-type: application/json" \
  -d "{\"email\": \"myname@mail.com\", \"password\": \"123123\" }"

Ответ должен быть примерно следующим:

{
    "data": {
        "id":1,
        "name":"admin",
        "email":"test@mail.com",
        "created_at":"2019-02-15 01:15:00",
        "updated_at":"2019-02-15 01:40:00",
        "api_token":"Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCX6pwAeZ7JR6YP5zQqnw"
    }
}

Для того, чтобы отослать токен, вы можете отсылать его как дополнительный заголовок при запросе:

Authorization: Bearer Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw

Выход пользователя в RESTful API для Laravel 5

По нашей идее, если токен отсутствует или пропущен, пользователь должен получать сообщение о том, что он не авторизован. При этом мы будем удалять токен в базе данных:

Файл 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);
}

Используя такой подход, если пользователь прислал неправильный токен, ему будет закрыт доступ. Это нужно для того, чтобы не допустить передачи данных пользователю, к которым у него нет досутпа.

Использование Middleware в RESTful API (Laravel) для запрета доступа

Теперь мы можем использовать middleware для ограниченя доступа пользователя к ресурсам:

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

Мы можем получить доступ к текущему пользователю, используя метод $request->user() или через фасад Auth:

Auth::guard('api')->user(); // залогиненный пользователь
Auth::guard('api')->check(); // авторизован ли пользователь
Auth::guard('api')->id(); // id авторизованного пользователя

Но при попытке использования мы получим ошибку: InvalidArgumentException in UrlGenerator.php, line 304.

Это происходит потому, что мы должны отредактировать метод unauthenticated() в нашем классе обработчика. Текущая версия возвращает JSON только если в запросе был передан заголовок Accept: application/json, поэтому изменим это:

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

Теперь давайте вернемся к началу статьи и добавим middleware в наши роуты, чтобы они тоже работали с авторизацией:

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');
});

Таким образом нам не придется добавлять middleware к каждому роуту. Подробности по этому поводу вы можете прочитать в моей статье роуты в Laravel 5.

Тестирование RESTful API в Laravel 5

Laravel по умолчанию содержит PHPUnit для тестирования наших проектов. Также фреймворк предоставляет нам несколько инструментов и средств для тестирования нашего приложения, особенно для тестирования API.

Конечно, вы можете использовать другие инструменты для тестирования API, однако тестирования встроенными средствами Laravel принесет больше пользы: мы можем использовать его на всю катушку, имя полный контроль над приложением и тестами. Например, мы можем автоматически изменять точки доступа (endpoints), не прописывая их в тестах каждый раз заново.

Для начала мы должны настроить SQLite. Использование этой базы данных сделает наши тесты очень быстрыми. Кроме того, мы не затронем основную базу данных проекта. Это позволит нам каждый раз устанавливать базу данных "с чистого листа" только для тестирования!

Отредактируем файл config/database.php следующим образом:

...
'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 и немного изменить метод 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');
    }
}

Осталось только изменить composer.json для запуска тестов:

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

Запуск тестов можно произвести следующей командой:

$ composer test

Создание фабрики для тестирования RESTful API в Laravel 5

Фабрики позволят нам быстр создавать объекты для тестирования правильных данных в наших тестах. Они находятся в каталоге database/factories. Давайте добавим одну для нашей модели Article:

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

Faker уже подключен, поэтому об этом нам заботиться не нужно.

Пишем наши тесты!

Давайте создадим наш первый тест, который будет проверять возможность входа пользователя. Используем следующую команду:

$ 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() запрашивает данные с эндпоинта, assertStatus() проверяет код ответа, а assertJson() проверяет, что структура полученного JSON совпадает с ожидаемой.

Теперь давайте создадим тест для регистрации пользоватея:

$ 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()
    {
        $user = factory(User::class)->create(['email' => 'user@test.com']);
        $token = $user->generateToken();
        $headers = ['Authorization' => "Bearer $token"];

        $user->api_token = null;
        $user->save();

        $this->json('get', '/api/articles', [], $headers)->assertStatus(401);
    }
}

Важно заметить, что во время тестирования Laravel не перезапускается при новом запросе. Поэтому если вы уже авторизовались, то Laravel запомнит это состояние. Таким образом, мы должны разделить тест выхода на два, чтобы избежать этих проблем с кешированием.

А вот тест, который проверяет эндпоинты для ресурса 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'],
            ]);
    }

}

Что еще почитать?

В заключение хочу порекомендовать вам почитать про Laravel Passport. Успехов.

Оригинал статьи: https://www.toptal.com/laravel/restful-laravel-api-tutorial

Комментарии

Комментариев пока нет. Станьте первым! Конечно, если вы не хотите написать мне гадости.
Обязательное поле.
Подсказка по Markdown, используется для оформления.

DeveloperNotes.ru © 2018 — 2020