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

Данная статья является продолжением статьи Laravel RESTful API, в которой было описано создание REST API с помощью фреймворка Laravel.

Итак продолжим.

Laravel включает в себя интеграцию с PHPUnit прямо из коробки. Фреймворк предоставляет нам несколько хелперов, которые упрощают тестирование наших API.

Существует ряд внешних инструментов, которые вы можете использовать для тестирования вашего API; однако тестирование внутри Laravel является гораздо лучшей альтернативой — у нас есть все преимущества тестирования структуры и результатов API, сохраняя при этом полный контроль над базой данных.

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

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

В конфигурационном файле config/database.php, мы должны установить поле database  :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, выполнить миграции и заполнить базу данными(seed).

Для этого, нам необходимо добавить трейт 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"
        ],
    ... 
    },    

Теперь команда test доступна подобным образом:


$ composer test

Установка Factories в тестах

Factories(фабрики) позволяют нам быстро создавать объекты. Они находятся в папке database/factories.

Laravel «из коробки» поставляется с фабрикой для класса Users, давайте добавим одну для класса Article:


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

Библиотека Faker уже введена, чтобы помочь нам создать правильный формат случайных данных для наших моделей.

Первый тест

Мы будем использовать методы 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' => '[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',
                ],
            ]);

    }
}

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


$ 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.'],
            ]);
    }
}

И наконец для точки выхода(Logout)


$ 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 не создается заново с каждым новым запросом.
Это означает, что когда мы сталкиваемся с authentication middleware, он сохраняет текущего пользователя внутри экземпляра 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'],
            ]);
    }

}

На этом все!

 

Оригинал статьи Laravel API Tutorial: How to Build and Test a RESTful API

One Reply to “Laravel RESTful API. Тестирование”

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *