Как оптимизировать Feature tests Laravel?
Ниже в коде у меня повторяется код а именно:
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' => '[email protected]',
'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' => '[email protected]',
'password' => 'password',
'password_confirmation' => 'password'
]);
$response = $this->post('api/auth/login', [
'email' => '[email protected]',
'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' => '[email protected]',
'password' => 'password',
'password_confirmation' => 'password'
]);
$res = $this->post('api/auth/login', [
'email' => '[email protected]',
'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);
}
}
Ответы (1 шт):
Автор решения: InDevX
→ Ссылка
- данные юзера вынести в отдельный метод:
protected function getUserRegistrationData(): array
{
return [
'name' => 'userone',
'email' => '[email protected]',
'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
];
}
- тест регистрации:
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
}
- тест логина:
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);
]);
}
- тест 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);
}
- Желательно покрывать все кейсы тестами:
регистрация:
- успешная
- не успешная:
// если исп. 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')
);
}