تطوير الويب

اختبار تطبيق Laravel 11 بـ Pest: دليل خطوة بخطوة

5 min de lecture

📍 الدليل المرجعي: Laravel 11 وPHP 8.4

تطبيق بلا اختبارات تطبيق لا تستطيع تعديل كوده بثقة. Laravel 11 يضمّ Pest افتراضيًّا في installer التفاعلي، إشارة إلى أنّ منظومة PHP اعتمدت هذا الـ framework كمعيار فعلي. ملاحظة (مايو 2026): Pest 4 صدر حديثًا ويجلب browser testing كميزة أبرز؛ أوامر التثبيت والمفاهيم المُغطّاة هنا (mutation testing، arch presets، datasets) صالحة في Pest 3 وPest 4. Pest يجلب صياغة مُعَبِّرة موروثة من Jest وRSpec، تقارير ملوّنة قابلة للقراءة، mutation testing لتقييم جودة سلسلتك، وarch presets للتحقّق من معمارية كودك.

المتطلّبات

  • مشروع Laravel 11.
  • على الأقلّ نموذج وcontroller مُنشأَين.
  • أساسيات الاختبار (assertion، mock، factory).
  • الوقت: 45 إلى 60 دقيقة.

الخطوة 1 — تثبيت Pest 3 في مشروع Laravel 11 قائم

# تثبيت Pest 3 وplugin Laravel
composer require pestphp/pest pestphp/pest-plugin-laravel --dev --with-all-dependencies

# تهيئة Pest
./vendor/bin/pest --init

الأمر pest --init يُنشئ tests/Pest.php ويُكَيِّف phpunit.xml لاستعمال Pest كـ runner. يُنشئ أيضًا مجلّدَي tests/Unit/ وtests/Feature/.

./vendor/bin/pest

المتوقّع: تقرير أخضر بـ Tests: 2 passed.

الخطوة 2 — صياغة Pest: it()، test()، وexpect()

<?php
// صياغة PHPUnit
class ArticleTest extends TestCase
{
    public function test_article_has_a_title(): void
    {
        $article = new Article(['title' => 'Mon article']);
        $this->assertEquals('Mon article', $article->title);
    }
}

// صياغة Pest — مكافئ دقيق
it('has a title', function () {
    $article = new Article(['title' => 'Mon article']);
    expect($article->title)->toBe('Mon article');
});

// صياغة Pest مع test() — للوصف الأطول
test('un article sans titre est invalide', function () {
    $article = Article::factory()->make(['title' => '']);
    expect($article->title)->toBeEmpty();
});

الدالّة expect() هي قلب Pest: تُغَلِّف قيمة وتعرض API سلسلة من matchers (toBe()، toEqual()، toBeNull()، toContain()، toHaveCount()).

الخطوة 3 — اختبارات وحدوية: نماذج وخدمات

php artisan pest:test Unit/ArticleTest --unit
<?php
// tests/Unit/ArticleTest.php

use App\Models\Article;
use Illuminate\Support\Str;

it('génère un slug à partir du titre', function () {
    $article = Article::factory()->make(['title' => 'Mon Premier Article 2025']);
    expect($article->slug)->toBe('mon-premier-article-2025');
});

it('est en statut draft par défaut', function () {
    $article = new Article();
    expect($article->status)->toBe('draft');
});

it('date de publication est une instance Carbon quand castée', function () {
    $article = Article::factory()->make([
        'published_at' => '2026-01-15 10:00:00',
    ]);
    expect($article->published_at)->toBeInstanceOf(Carbon\Carbon::class);
});

// Dataset
it('slugifie correctement les titres accentués', function (string $title, string $expectedSlug) {
    $article = Article::factory()->make(['title' => $title]);
    expect($article->slug)->toBe($expectedSlug);
})->with([
    'titre avec accents'      => ['Hébergement VPS à Dakar', 'hebergement-vps-a-dakar'],
    'titre avec caractères spec' => ['API REST : guide 2025', 'api-rest-guide-2025'],
    'titre en majuscules'      => ['NODEJS AVANCÉ', 'nodejs-avance'],
]);

الميزة with() (datasets) من أقوى ما في Pest: تُنَفِّذ نفس الاختبار مع جداول بيانات متعدّدة دون تكرار جسد الاختبار.

./vendor/bin/pest --filter=Unit

الخطوة 4 — اختبارات وظيفية HTTP بـ RefreshDatabase

<!-- phpunit.xml -->
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
php artisan pest:test Feature/ArticleApiTest
<?php
// tests/Feature/ArticleApiTest.php

use App\Models\Article;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

it('retourne la liste paginée des articles publiés', function () {
    Article::factory()->count(5)->published()->create();
    Article::factory()->count(3)->create(['status' => 'draft']);

    $response = $this->getJson('/api/articles');

    $response->assertOk()
        ->assertJsonStructure([
            'data' => [['id', 'title', 'slug', 'status', 'author']],
            'meta' => ['current_page', 'total', 'per_page'],
        ])
        ->assertJsonCount(5, 'data');
});

it('un utilisateur authentifié peut créer un article', function () {
    $user = User::factory()->create();

    $response = $this->actingAs($user)->postJson('/api/articles', [
        'title'   => 'Mon nouvel article de test',
        'content' => str_repeat('Contenu de test. ', 10),
    ]);

    $response->assertCreated()
        ->assertJsonPath('data.title', 'Mon nouvel article de test')
        ->assertJsonPath('data.status', 'draft');

    $this->assertDatabaseHas('articles', [
        'title'   => 'Mon nouvel article de test',
        'user_id' => $user->id,
    ]);
});

it("un utilisateur non authentifié ne peut pas créer d'article", function () {
    $this->postJson('/api/articles', [
        'title'   => 'Article non autorisé',
        'content' => str_repeat('x', 50),
    ])->assertUnauthorized();
});

it('refuse la création sans titre', function () {
    $user = User::factory()->create();

    $this->actingAs($user)->postJson('/api/articles', [
        'content' => str_repeat('Contenu valide. ', 5),
    ])->assertUnprocessable()
       ->assertJsonValidationErrors(['title']);
});

trait RefreshDatabase يضمن أن يبدأ كلّ اختبار بقاعدة نظيفة. actingAs($user) يُحاكي جلسة موثَّقة دون المرور بتدفّق الاتّصال.

States factory: الـ state published() يجب تعريفه في ArticleFactory:

<?php
public function published(): self
{
    return $this->state(fn (array $attributes) => [
        'status'       => 'published',
        'published_at' => now(),
    ]);
}

الخطوة 5 — اختبار routes Sanctum بـ actingAs

<?php
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;

uses(RefreshDatabase::class);

it('émet un token lors de la connexion', function () {
    $user = User::factory()->create();

    $this->postJson('/api/login', [
        'email'       => $user->email,
        'password'    => 'password',
        'device_name' => 'test-device',
    ])->assertOk()
      ->assertJsonStructure(['token']);
});

it('accède aux ressources protégées avec un token Sanctum valide', function () {
    $user = User::factory()->create();

    Sanctum::actingAs($user, ['articles:read']);

    $this->getJson('/api/user')->assertOk()->assertJsonPath('id', $user->id);
});

it('refuse l\'accès sans token Sanctum', function () {
    $this->getJson('/api/user')->assertUnauthorized();
});

الخطوة 6 — Architecture Testing مع Arch Presets Laravel

<?php
// tests/Arch/LaravelArchTest.php

arch()->preset()->laravel();

arch('les contrôleurs ne contiennent pas de logique métier directe')
    ->expect('App\Http\Controllers')
    ->not->toUseStrict()
    ->not->toHavePublicMethods();

arch('les modèles étendent Eloquent Model')
    ->expect('App\Models')
    ->toExtend('Illuminate\Database\Eloquent\Model');

arch('les jobs implémentent ShouldQueue')
    ->expect('App\Jobs')
    ->toImplement('Illuminate\Contracts\Queue\ShouldQueue');

هذه الاختبارات تُخفق صامتًا إن انحرفت معماريّتك — مثلًا إن أضاف مطوّر استعلام SQL مباشر في controller بدل Eloquent.

الخطوة 7 — تشغيل الاختبارات وتفسير التقارير

# كلّ الاختبارات
./vendor/bin/pest

# تغطية الكود
./vendor/bin/pest --coverage --min=80

# وضع watch (إعادة تشغيل مع كلّ حفظ)
./vendor/bin/pest --watch

# تصفية باسم
./vendor/bin/pest --filter="retourne la liste"

# ملفّ واحد
./vendor/bin/pest tests/Feature/ArticleApiTest.php

# وضع متوازٍ
./vendor/bin/pest --parallel

أخطاء شائعة

الخطأ السبب الحلّ
Class RefreshDatabase not found نسيان uses(RefreshDatabase::class) أضف uses(RefreshDatabase::class);
اختبارات بطيئة SQLite ملفّ بدل :memory: ضع DB_DATABASE=:memory:
actingAs لا يحمي routes Sanctum استعمال actingAs() بدل Sanctum::actingAs() استورد use Laravel\Sanctum\Sanctum;
Factory غير موجود نموذج دون -f أو trait HasFactory أضف use HasFactory; وأنشئ factory

FAQ

Pest متوافق مع PHPUnit القائم؟ نعم. Pest يعمل فوق PHPUnit. classes PHPUnit الكلاسيكية تعمل دون تعديل في مشروع Pest.

كيف نُهَيِّئ RefreshDatabase شموليًّا؟ في tests/Pest.php: uses(RefreshDatabase::class)->in('Feature');.

ما Mutation Testing في Pest 3؟ يُدخل تعديلات صغيرة في كودك (قلب === إلى !==، حذف return) ويتحقّق من اكتشاف اختباراتك لها. ./vendor/bin/pest --mutate.

الأدلّة الأخرى

🔝 العودة للدليل الرئيسي

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité