Как оптимизировать Feature tests Laravel?

Рейтинг: 0Ответов: 1Опубликовано: 31.05.2023
Ниже в коде у меня повторяется код а именно:
1 Artisan::call('passport:install');
2 регистрация
2 авторизация

Писали что тесты не должны быть зависимыми поэтому сделал каждый независимым
но может как то можно или нужно переиспользовать код?


<?php
declare(strict_types=1);

namespace Tests\Feature;

use Illuminate\Support\Facades\Artisan;
use Illuminate\Foundation\Testing\RefreshDatabase;

use Tests\TestCase;

class AuthTest extends TestCase
{
    use RefreshDatabase;
    
    /**
     * Register user.
     *
     * @return void
     */
    public function test_register(): void
    {
        $response = $this->post('api/auth/register', [
            'name' => 'userone',
            'email' => 'userone@mail.com',
            'password' => 'password',
            'password_confirmation' => 'password'
        ]);
        $response->assertNoContent($status = 204);
    }
    
    /**
     * Login.
     *
     * @return void
     */
    public function test_login(): void
    {
        Artisan::call('passport:install');
        
        $this->post('api/auth/register', [
            'name' => 'userone',
            'email' => 'userone@mail.com',
            'password' => 'password',
            'password_confirmation' => 'password'
        ]);
        $response = $this->post('api/auth/login', [
            'email' => 'userone@mail.com',
            'password' => 'password',
            'device_name' => 'Iphone 14 PRO MAX'
        ]);
        $response->assertOk();
    }
    
    /**
     * Logout.
     *
     * @return void
     */
    public function test_logout(): void
    {
        Artisan::call('passport:install');
        
        $this->post('api/auth/register', [
            'name' => 'usertwo',
            'email' => 'usertwo@mail.com',
            'password' => 'password',
            'password_confirmation' => 'password'
        ]);
        $res = $this->post('api/auth/login', [
            'email' => 'usertwo@mail.com',
            'password' => 'password',
            'device_name' => 'Iphone 14 PRO MAX'
        ]);
        $dataToken = json_decode($res->getContent())->data;
        $response = $this->post('api/auth/logout', 
            ['token_id' => $dataToken->id], 
            ['Authorization' => 'Bearer ' . $dataToken->token]
        );
        $response->assertNoContent($status = 204);
    }
}

Ответы

▲ 0Принят
  1. данные юзера вынести в отдельный метод:
protected function getUserRegistrationData(): array
{
    return [
        'name' => 'userone',
        'email' => 'userone@mail.com',
        'password' => 'password',
        'password_confirmation' => 'password'
    ];
}

protected function getUserResponseData(\App\Models\User $user): array
{
    return [
        'id' => $user->id,
        'name' => $user->name,
        'email' => $user->email,
        'token' => $user->token,
        'created_at' => $user->created_at,
        'updated_at' => $user->updated_at
    ];
}

  1. тест регистрации:
public function test_registration(): void
{
    $response = $this->post('api/auth/register', $this->getUserRegistrationData());
    // $response->assertNoContent($status = 204);
    // это не Ок что регистрация 204 выдаёт... 
    // если не api - можно, напр. редирект на страницу логина:
    // $response->assertStatus(302);
    // $response->assertRedirectedToRoute('login');
    // но поскольку ссылка начинается с api то и возвращать должна ресурс/данные
    // поэтому в тестах сравниваем их - вся суть, сравнивать фактическое и ожидаемое
    // предполагаю такой массив возвращает роут (просто пример):
    $response->assertJson([
        'data' => $this->getUserResponseData(\App\Models\User::find(1));
    ]);
    $response->assertSuccessful();

    // если модель создается при регистрации 
    // то можно ещё добавить проверку на существовании новой записи:
    $this->assertTrue(\App\Models\User::exists()); //  \App\Models\User::count() === 1
}
  1. тест логина:
public function test_login(): void
{
    // не знаю что делает passport:install, но 
    // не вижу проблем вызывать у каждого теста
    Artisan::call('passport:install');

    $user = \App\Models\User::create($this->getUserRegistrationData());
    // если токен или что-то ещё добавляется после создания модели то:
    // вынести всё это дело в фабрику и там делать, 
    // или в тестах создать отдельный метод и его вызывать, аля:
    // $this->setToken($user);

    $response = $this->post('api/auth/login', [
        'email' => $user->email,
        'password' => $this->getUserRegistrationData()['password'],
        'device_name' => 'Iphone 14 PRO MAX'
    ]);
    $response->assertSuccessful();
    // просто пример - если логин возвращает view:
    // $response->assertViewIs('view.name');
    // если тот же редирект, напр. на домашнюю страницу, то:
    // $response->assertStatus(302);
    // $response->assertRedirectedToRoute('home');
    
    // но опять таки, поскольку ссылка начинается 
    // с api то и возвращать должна ресурс/данные.
    // предполагаю такой массив возвращает роут (просто пример):
    $response->assertJson([
        'data' => $this->getUserResponseData($user);
    ]);
}
  1. тест logout:
public function test_logout(): void
{
    Artisan::call('passport:install');

    $user = \App\Models\User::create($this->getUserRegistrationData());
    
    // как я понимаю - при логине что-то происходит, 
    // и возвращаются данные с новым токеном
    // в контроллере урла api/auth/logout какой-то метод формирует этот токен.
    // нам в тест нужно добавить такой же метод, можно модифицировать как-то
    // и в тестах юзать этот метод, аля:
    $tokenData = $this->getTokenData($user);
    
    $response = $this->post('api/auth/logout', 
        ['token_id' => $tokenData->id], 
        ['Authorization' => 'Bearer ' . $tokenData->token]
    );
    // для логаута, в принципе, норм 204
    $response->assertNoContent($status = 204); 
}
  1. Желательно покрывать все кейсы тестами:
регистрация:
- успешная
- не успешная: 
  // если исп. FormRequest то ошибка пропущенного name:
  // $response->assertValidationErrors('name') (аналогично остальные)
  * пропушен параметр name
  * пропушен параметр email
  * пропушен параметр password
  * password и confirm password не совпадают

логин:
- успешный
- не успешный
  * пропушен параметр email
  * пропушен параметр password
  * пропушен параметр device_name
  // и в зависимости как именно логин проходит, можно добавить:
  // * email/пароль не существующий / не подоходящий

логаут:
- успешный
- не успешный
  * пропущен/не верный параметр token_id
  пропущен/не верный заголовок Authorization

* для обязательных параметров

P.S. могу где-то ошибаться, имхо, ответ открыт для исправлений/дополнений

UPD. по токену, сильно упрощенный пример метода с "боевого проекта":

protected function getToken(User $user): string
{
    return JWT::encode([
            'name' => $user->username,
            'firstName' => $user->first_name,
            'lastName' => $user->last_name,
            'middleName' => $user->middle_name,
            'email' => $user->email,
        ],
        config('auth.jwt_secret'), 
        config('auth.jwt_alg')
    );
}