• Час читання ~3 хв
  • 10.08.2022

Фабрики моделей Laravel є однією з найкращих функцій, які ви можете використовувати у своїй програмі, коли мова заходить про тестування. Вони забезпечують спосіб визначення даних, які є передбачуваними та легкими для повторення, щоб ваші тести були послідовними та контрольованими.

Почнемо з простого прикладу. У нас є програма, яка використовується для ведення блогів, тому, природно, у нас є Публікаціямодель, яка має статус, якщо публікація опублікована, чернеткова або поставлена ​​в чергу. Давайте розглянемо модель Eloquent для цього прикладу:

declare(strict_types=1);
 
namespace App\Models;
 
use App\Publishing\Enums\PostStatus;
use Illuminate\Database\Model;
 
class Post extends Model
{
	protected $fillable = [
		'title',
		'slug',
		'content',
		'status',
		'published_at',
	];
 
	protected $casts = [
		'status' => PostStatus::class,
		'published_at' => 'datetime',
	];
}

Як ви можете бачити тут, у нас є Enum для стовпця статусу, який ми зараз розробимо. Використання переліку тут дозволяє нам скористатися перевагами функцій PHP 8.1 замість простих рядків, логічних прапорів або безладних переліків бази даних.

declare(strict_types=1);
 
namespace App\Publishing\Enums;
 
enum PostStatus: string
{
	case PUBLISHED = 'published';
	case DRAFT = 'draft';
	case QUEUED = 'queued';
}

Тепер давайте повернемося до теми, яку ми тут обговорили: модельні фабрики. Проста фабрика виглядатиме дуже просто:

declare(strict_types=1);
 
namespace Database\Factories;
 
use App\Models\Post;
use App\Publishing\Enums\PostStatus;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
 
class PostFactory extends Factory
{
	protected $model = Post::class;
 
	public function definition(): array
	{
		$title = $this->faker->sentence();
		$status = Arr::random(PostStatus::cases());
 
		return [
			'title' => $title,
			'slug' => Str::slug($title),
			'content' => $this->faker->paragraph(),
			'status' => $status->value,
			'published_at' => $status === PostStatus::PUBLISHED
				? now()
				: null,
		];
	}
}

Тож у наших тестах тепер ми можемо швидко зателефонувати на нашу поштову фабрику, щоб створити для нас пошту. Давайте подивимося, як ми можемо це зробити:

it('can update a post', function () {
	$post = Post::factory()->create();
 
	putJson(
		route('api.posts.update', $post->slug),
		['content' => 'test content',
	)->assertSuccessful();
 
	expect(
		$post->refresh()
	)->content->toEqual('test content');
});

Достатньо простий тест, але що станеться, якщо у нас є бізнес-правила, які стверджують, що ви можете оновлювати лише певні стовпці залежно від тип публікації?Давайте переробимо наш тест, щоб переконатися, що ми можемо це зробити:

it('can update a post', function () {
	$post = Post::factory()->create([
		'type' => PostStatus::DRAFT->value,
	]);
 
	putJson(
		route('api.posts.update', $post->slug),
		['content' => 'test content',
	)->assertSuccessful();
 
	expect(
		$post->refresh()
	)->content->toEqual('test content');
});

Чудово, ми можемо передати аргумент у метод create, щоб переконатися, що ми встановлюємо правильний тип, коли ми створюємо це так, щоб наші бізнес-правила не скаржилися. Але продовжувати писати трохи громіздко, тому давайте трохи відремонтуємо нашу фабрику, щоб додати методи для зміни стану:

declare(strict_types=1);
 
namespace Database\Factories;
 
use App\Models\Post;
use App\Publishing\Enums\PostStatus;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
 
class PostFactory extends Factory
{
	protected $model = Post::class;
 
	public function definition(): array
	{
		$title = $this->faker->sentence();
 
		return [
			'title' => $title,
			'slug' => Str::slug($title),
			'content' => $this->faker->paragraph(),
			'status' => PostStatus::DRAFT->value,
			'published_at' => null,
		];
	}
 
	public function published(): static
	{
		return $this->state(
			fn (array $attributes): array => [
				'status' => PostStatus::PUBLISHED->value,
				'published_at' => now(),
			],
		);
	}
}

Ми встановлюємо параметри за умовчанням для нашої фабрики, щоб усі новостворені публікації були чернетками. Потім ми додаємо метод встановлення стану для публікації, який використовуватиме правильне значення Enum і встановлюватиме дату публікації – набагато більш передбачуваний і повторюваний у середовищі тестування. Давайте подивимося, як тепер виглядатиме наш тест:

it('can update a post', function () {
	$post = Post::factory()->create();
 
	putJson(
		route('api.posts.update', $post->slug),
		['content' => 'test content',
	)->assertSuccessful();
 
	expect(
		$post->refresh()
	)->content->toEqual('test content');
});

Повернемося до простого тесту – отже, якщо у нас є кілька тестів, які хочуть створити чернетку публікації, вони можуть використовувати фабрику. Тепер давайте напишемо тест для опублікованого стану та перевіримо, чи ми отримаємо помилку.

it('returns an error when trying to update a published post', function () {
	$post = Post::factory()->published()->create();
 
	putJson(
		route('api.posts.update', $post->slug),
		['content' => 'test content',
	)->assertStatus(Http::UNPROCESSABLE_ENTITY());
 
	expect(
		$post->refresh()
	)->content->toEqual($post->content);
});

Цього разу ми перевіряємо, чи ми отримуємо статус помилки перевірки під час спроби щоб оновити опубліковану публікацію.Це гарантує, що ми захищаємо наш вміст і запроваджуємо певний робочий процес у нашій програмі.

То що станеться, якщо ми також захочемо забезпечити певний вміст на нашій фабриці? За потреби ми можемо додати інший метод зміни стану:

declare(strict_types=1);
 
namespace Database\Factories;
 
use App\Models\Post;
use App\Publishing\Enums\PostStatus;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
 
class PostFactory extends Factory
{
	protected $model = Post::class;
 
	public function definition(): array
	{
		return [
			'title' => $title = $this->faker->sentence(),
			'slug' => Str::slug($title),
			'content' => $this->faker->paragraph(),
			'status' => PostStatus::DRAFT->value,
			'published_at' => null,
		];
	}
 
	public function published(): static
	{
		return $this->state(
			fn (array $attributes): array => [
				'status' => PostStatus::PUBLISHED->value,
				'published_at' => now(),
			],
		);
	}
 
	public function title(string $title): static
	{
		return $this->state(
			fn (array $attributes): array => [
				'title' => $title,
				'slug' => Str::slug($title),
			],
		);
	}
}

Тож у наших тестах ми можемо створити новий тест, який гарантує, що ми можемо оновлювати чернетки публікацій назву через наш API:

it('can update a draft posts title', function () {
	$post = Post::factory()->title('test')->create();
 
	putJson(
		route('api.posts.update', $post->slug),
		['title' => 'new title',
	)->assertSuccessful();
 
	expect(
		$post->refresh()
	)->title->toEqual('new title')->slug->toEqual('new-title');
});

Таким чином, ми можемо контролювати речі в нашому тестовому середовищі, використовуючи фабричні стани, надаючи стільки контролю, скільки нам потрібно. Це забезпечить послідовну підготовку наших тестів або буде гарним відображенням стану додатків у певних точках.

Що ми робимо, якщо нам потрібно створити багато моделей для наших тестів? Як ми можемо це зробити? Легкою відповіддю було б сказати заводу:

it('lists all posts', function () {
	Post::factory(12)->create();
 
	getJson(
		route('api.posts.index'),
	)->assertOk()->assertJson(fn (AssertableJson $json) =>
		$json->has(12)->etc(),
	);
});

Тож ми створюємо 12 нових дописів і гарантуємо, що коли ми отримаємо маршрут індексу, ми матимемо 12 повідомлень, що повертаються. Замість того, щоб передати підрахунок у заводський метод, ви також можете використати метод підрахунку:

Post::factory()->count(12)->create();

Однак у нашій програмі бувають випадки, коли ми можемо захотіти запустити щось у конкретне замовлення. Скажімо, ми хочемо, щоб перший був чернеткою, а другий опубліковано?

it('shows the correct status for the posts', function () {
	Post::factory()
		->count(2)
		->state(new Sequence(
			['status' => PostStatus::DRAFT->value],
			['status' => PostStatus::PUBLISHED->value],
		))->create();
 
	getJson(
		route('api.posts.index'),
	)->assertOk()->assertJson(fn (AssertableJson $json) =>
		$json->where('id', 1)
			->where('status' PostStatus::DRAFT->value)
			->etc();
	)->assertJson(fn (AssertableJson $json) =>
		$json->where('id', 2)
			->where('status' PostStatus::PUBLISHED->value)
			->etc();
	);
});

Як ви використовуєте фабрики моделей у своїй програмі? Ви знайшли якісь цікаві способи їх використання? Повідомте нас у Твіттері!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297