
September 09, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Laravel Testing with Pest in CI/CD is how you stop broken code from reaching production on a real client project. Pest gives you expressive tests that read like specs, and your pipeline runs them on every push before a deploy is allowed. If you already ship Laravel with GitLab CI pipelines for Laravel, adding Pest is a small change with a big payoff. This guide covers install, pipeline config, caching, coverage gates, and the failures I see most often in 2026.
php artisan test (or ./vendor/bin/pest) inside your CI job on every push, failing the pipeline on any test failure, and only deploying when the suite passes on PHP 8.3+ with a real database service.What is Laravel Testing with Pest in CI/CD and why does it matter?
Pest is a testing framework built on PHPUnit. It wraps Laravel's test runner with a cleaner syntax. CI/CD is the automation layer that runs those tests before merge or deploy. Together they form a safety net you can trust.
On production Laravel applications I maintain, the pattern is simple. A developer pushes code. The pipeline installs dependencies, prepares a test database, runs Pest, and blocks the merge if anything fails. Deploy only happens after green tests. That is not optional for booking systems, payment flows, or legal-tech portals where a regression costs real money.
Pest works with Laravel 12 and Laravel 13. Laravel 13 requires PHP 8.3 or higher. Laravel 12 runs on PHP 8.2+. Most CI images ship PHP 8.4 or 8.5 today, which is fine. Match your CI PHP version to production. A version mismatch is a common source of false greens or false reds.
If you are still on PHPUnit syntax, read the Pest migration from PHPUnit guide first. The CI wiring stays almost identical because Pest delegates to PHPUnit under the hood.
How do you install Pest and write your first CI-ready Laravel tests?
Start in a Laravel 12 or 13 project on PHP 8.3+. Install Pest with Composer 2.10:
composer require pestphp/pest --dev --with-all-dependencies
composer require pestphp/pest-plugin-laravel --dev
php artisan pest:install The installer creates tests/Pest.php and updates phpunit.xml. Your CI job will call the same entry point Laravel ships:
php artisan test That command works for both Pest and PHPUnit tests during a gradual migration. A minimal feature test for a booking route might look like this:
<?php
use App\Models\User;
it('redirects guests away from the dashboard', function () {
$this->get('/dashboard')
->assertRedirect('/login');
});
it('shows the dashboard for authenticated users', function () {
$user = User::factory()->create();
$this->actingAs($user)
->get('/dashboard')
->assertOk()
->assertSee('Dashboard');
}); Configure phpunit.xml for CI
Your phpunit.xml must use environment variables the pipeline sets. Never hard-code production credentials. A CI-safe baseline:
<env name="APP_ENV" value="testing"/>
<env name="APP_KEY" value="base64:TESTKEYTESTKEYTESTKEYTESTKEYTESTKEYTEST="/>
<env name="DB_CONNECTION" value="mysql"/>
<env name="DB_HOST" value="127.0.0.1"/>
<env name="DB_DATABASE" value="laravel_test"/>
<env name="DB_USERNAME" value="root"/>
<env name="DB_PASSWORD" value="secret"/>
<env name="CACHE_STORE" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/> Use RefreshDatabase on feature tests that touch the database. For heavier suites, consider the parallel test runs with ParaTest approach once your test count grows past a few hundred.
Local parity before CI
Run the same command locally that CI runs. Match PHP extensions too. If CI uses pdo_mysql and your laptop uses SQLite, you will chase environment-only failures for weeks.
- Install Pest and the Laravel plugin.
- Write at least one unit test and one feature test.
- Configure
phpunit.xmlwith testing env vars. - Run
php artisan testlocally until green. - Add the same command to your CI job.
How do you configure GitLab CI for Laravel Pest tests?
GitLab CI is what I use on several sister sites sharing a Deployer 7 pipeline. The test stage belongs before build or deploy. Here is a production-ready .gitlab-ci.yml excerpt for Laravel 13 on PHP 8.4 with MySQL 8.4:
stages:
- test
- deploy
variables:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel_test
DB_CONNECTION: mysql
DB_HOST: mysql
DB_DATABASE: laravel_test
DB_USERNAME: root
DB_PASSWORD: secret
pest:
stage: test
image: php:8.4-cli
services:
- name: mysql:8.4
alias: mysql
cache:
key: ${CI_COMMIT_REF_SLUG}-composer
paths:
- vendor/
before_script:
- apt-get update && apt-get install -y git unzip libzip-dev libpng-dev
- docker-php-ext-install pdo_mysql zip gd
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- cp .env.testing .env
- composer install --no-interaction --prefer-dist --no-progress
- php artisan key:generate
- php artisan migrate --force
script:
- php artisan test --parallel
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH Cache vendor/ to speed up runs. See CI/CD caching for Composer and npm installs for key strategies that do not serve stale dependencies.
Keep secrets out of the YAML file. Use GitLab CI variables for anything sensitive. The CI/CD secrets management guide covers masked variables and rotation patterns I use on shared EC2 deploys.
How do you run Pest tests in GitHub Actions for Laravel?
GitHub Actions fits teams already on GitHub. The workflow mirrors GitLab CI. Pin actions to stable versions and use service containers for MySQL:
name: Laravel Pest Tests
on:
push:
branches: [main]
pull_request:
jobs:
pest:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.4
env:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel_test
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h 127.0.0.1"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
extensions: mbstring, pdo_mysql, zip, gd
coverage: xdebug
- uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('composer.lock') }}
- run: composer install --no-interaction --prefer-dist
- run: cp .env.testing .env
- run: php artisan key:generate
- run: php artisan migrate --force
env:
DB_HOST: 127.0.0.1
DB_PASSWORD: secret
- run: php artisan test --coverage --min=70 Read the full GitHub Actions for Laravel testing and deploy article for deploy job wiring. Split test and deploy into separate jobs. Deploy should need the test job via needs: pest.
Official references worth bookmarking: the Pest installation docs and the Laravel 13.x testing documentation.
Which CI/CD platform works best for Pest — GitLab CI or GitHub Actions?
Both run Pest equally well. The choice depends on where your repo lives and who maintains the runners. Here is a practical comparison for Laravel teams in 2026:
| Criterion | GitLab CI | GitHub Actions |
|---|---|---|
| Best fit | Self-hosted GitLab, Deployer pipelines, private runners | GitHub-native repos, open source, marketplace actions |
| MySQL service | services: block with alias | services: container with health check |
| Composer cache | Built-in cache: key per branch | actions/cache keyed on lock file hash |
| Parallel Pest | php artisan test --parallel | Same command, scale via matrix shards |
| Secrets | CI/CD variables, masked | Repository secrets, environments |
| Deploy gate | rules: + manual deploy stage | needs: + environment protection |
| Cost on small teams | Free tier minutes, self-hosted is cheap | 2,000 free minutes/month on private repos |
For VPS deploys after tests pass, pair either platform with the GitLab CI/CD to VPS guide or the GitHub Actions equivalent. The test stage stays the same. Only the deploy job changes.
How do you add coverage gates and catch flaky tests in CI?
Passing tests are the baseline. Coverage gates stop untested code from shipping silently. Pest supports coverage through PHPUnit's Xdebug or PCOV integration:
php artisan test --coverage --min=70 Set the minimum to what your team can sustain. Seventy percent is a reasonable starting point for a mature Laravel app. Read code coverage gates in CI before enforcing strict thresholds on legacy codebases.
Flaky test patterns I see in production
- Time-dependent assertions — use
Carbon::setTestNow()or freeze time in Pest'sbeforeEach. - Missing
RefreshDatabase— tests pass alone but fail in suite order. - External HTTP calls — mock with
Http::fake(); never hit live APIs in CI. - Filesystem assumptions — use
Storage::fake('local')for upload tests. - Race conditions in parallel runs — isolate tests that share Redis or cache keys.
For API-heavy apps, combine Pest feature tests with the patterns in Laravel feature testing best practices and building RESTful APIs with Laravel. Validate JSON shape, status codes, and auth middleware in one test file per endpoint group.
Database restore testing catches migration drift. The database restore testing guide shows how to verify backups against a fresh schema — a step many teams skip until production breaks.
What advanced Pest patterns help legal-tech and eCommerce Laravel apps?
On legal-tech portals and eCommerce systems, tests must cover more than happy paths. Document uploads, payment callbacks, and role-based access need dedicated Pest files.
Test payment webhooks without live gateways
it('marks an order paid when the webhook is valid', function () {
Http::fake();
$order = Order::factory()->create(['status' => 'pending']);
$payload = ['order_id' => $order->id, 'status' => 'paid'];
$this->postJson('/webhooks/khalti', $payload, [
'X-Signature' => validSignature($payload),
])->assertOk();
expect($order->fresh()->status)->toBe('paid');
}); Projects like Adventure Third Pole Trek and Mijar Law Associates depend on booking and payment flows that cannot break silently. Pest makes these scenarios readable for the next developer who touches the code.
Custom expectations for domain rules
Register reusable expectations in tests/Pest.php for Nepali date handling or VAT calculations. Your CI suite then enforces business rules on every push. For date logic, cross-check edge cases with the Nepali date converter tool during local test design.
If you need help wiring tests into an existing pipeline, the testing and optimization service covers audit, setup, and coverage improvement. For greenfield apps, start with enterprise application development where CI is planned from day one.
Key Takeaways
- Install Pest with the Laravel plugin, then run
php artisan test— the same command works locally and in CI. - Pin CI PHP to your production version and install
pdo_mysql,zip, andgdbefore Composer runs. - Use a real MySQL service container in CI; SQLite-only local dev causes false greens.
- Cache
vendor/keyed oncomposer.lockto keep pipeline times under five minutes. - Add
--coverage --min=70once your suite is stable; do not gate legacy code on day one. - Block deploy stages until the Pest job exits 0 — no manual override without a documented exception.
People Also Ask
Does Pest replace PHPUnit in Laravel?
Pest sits on top of PHPUnit. It changes syntax, not the underlying runner. Laravel's php artisan test command detects and runs Pest tests automatically. You can migrate file by file without breaking CI.
Can you run Pest tests in parallel in CI?
Yes. Use php artisan test --parallel after installing brianium/paratest. Parallel runs cut wall-clock time on large suites. Ensure tests do not share mutable global state or fixed cache keys.
What PHP version should CI use for Laravel 13?
Laravel 13 requires PHP 8.3 or higher. Pin CI to the same minor version as production. PHP 8.4 and 8.5 work in CI if production matches. Mismatched versions cause extension and deprecation surprises.
How long should a Laravel Pest CI job take?
A well-cached pipeline with under 300 tests should finish in three to eight minutes. If runs exceed fifteen minutes, add parallel execution, trim integration tests, or split jobs with a test matrix. Slow pipelines get ignored.
Ship with confidence
Laravel Testing with Pest in CI/CD turns your test suite from a local habit into a deploy gate. Install Pest, wire php artisan test into GitLab CI or GitHub Actions, cache Composer dependencies, and block deploys on failure. Start small with feature tests on critical paths — logins, bookings, payments — then expand coverage over time.
For teams without pipeline experience, read CI/CD best practices for small teams and GitLab CI/CD for PHP projects. Need hands-on help wiring Pest into your deploy flow? Contact us or explore the full web development service and recent Court Marriage in Nepal portfolio work built on the same GitLab CI + Deployer stack.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

