Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Fuzz Laravel Package

jonpurvis/fuzz

Fuzz is a Laravel package for fuzz testing your application. It sends randomized requests and payloads to your routes, helping uncover unhandled exceptions and 500 errors before they reach production.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install as a dev dependency.

    composer require jonpurvis/fuzz --dev
    

    Pest plugins that declare extra.pest.plugin in their composer.json are auto-registered — after composer dump-autoload you should see the ->fuzz() method available without editing tests/Pest.php. If not, check the README for the manual uses() registration line.

  2. Enable a coverage driver. Coverage-guided fuzzing is the whole point of the package, so it needs real coverage feedback:

    pecl install pcov      # preferred for speed
    # or ensure Xdebug is installed with coverage mode available
    

    Confirm with php -m | grep -E 'pcov|xdebug' and vendor/bin/pest --coverage before writing your first fuzz test — if coverage is broken, the fuzzer either errors or silently degrades to blind random testing.

  3. Look here first, in order:

    • vendor/jonpurvis/fuzz/README.md — the only reliable source of the exact API (config keys, seed handling, iteration limits).
    • vendor/jonpurvis/fuzz/tests/ — real, executable examples. Faster than the docs.
    • vendor/jonpurvis/fuzz/config/ — publish with php artisan vendor:publish --tag=fuzz-config if a config file is shipped.
    • vendor/jonpurvis/fuzz/src/ — the mutator/corpus classes tell you exactly what input shapes are supported.
  4. First use case — a pure parser/validator. Fuzzing pays off immediately on code that takes untrusted scalars and does branching:

    it('never fatals on arbitrary input', function (string $input) {
        $result = (new Slugger())->slug($input);
    
        expect($result)->toBeString()
            ->and($result)->not->toMatch('/\s/');
    })->fuzz();
    

    Run it with vendor/bin/pest --filter="never fatals". Failures print the input (and seed where supported) that broke the invariant — that input is your regression test.

Implementation Patterns

Invariant-first assertions, not exact-output assertions. Fuzzing can't know the "right" answer, so assert properties: round-trips, bounds, idempotency, "no exception of type X", "output is always a subset of input".

it('round-trips safely', function (string $s) {
    expect(base64_decode(base64_encode($s), true))->toBe($s);
})->fuzz();

Type-hinted parameters drive generation. The fuzzer inspects your test closure's parameters and produces inputs matching the declared types (string, int, array, etc.). This is the primary extension point — if you need a domain object, accept the primitive and construct the object inside the test body:

it('handles any email-ish string', function (string $raw) {
    $email = new EmailAddress($raw); // your constructor does the validating
    expect($email->domain())->toBeString();
})->fuzz();

Corpus-driven workflow for real bugs. Coverage-guided fuzzers keep inputs that hit new branches in a corpus directory. Commit that corpus (or persist it in CI cache) so each run starts where the last one ended — that is what turns a 30-second test into a cumulative bug hunter.

Isolate from the rest of your suite.

// tests/Pest.php
uses()->group('fuzz')->in('Fuzz');

Then: default vendor/bin/pest stays fast and deterministic; vendor/bin/pest --group=fuzz runs on demand or on a nightly CI schedule.

Cap the runtime for CI. Coverage-guided runs are open-ended. Prefer an iteration/time budget from the config file over "run until it finds something", and fail the job only on an actual crash so a long run doesn't red-flag a PR.

Layer it under existing unit tests. Keep your example-based tests for behavior; add one or two fuzz tests per "untrusted input" boundary (HTTP payload parsing, markdown/HTML sanitizers, CSV/JSON deserializers, money/number formatting, regex-heavy string utilities). Those are where coverage-guided mutation finds off-by-one and encoding bugs.

Reproduce failures deterministically. When a fuzz run fails, copy the reported input into a normal it('regression: input X', ...) test before fixing. The fuzzer will find the same class of bug again; the regression test makes sure it doesn't.

Pair with shrinking. Most fuzzers shrink a failing input to a minimal reproducer. Let it — a 12-byte failing string is a far better bug report than a 4KB one. Don't manually truncate the reported case.

Gotchas and Tips

  • No coverage driver, no guidance. With neither PCOV nor Xdebug, you're doing dumb fuzzing at best. This is the #1 cause of "the package doesn't find anything".
  • Xdebug is slow. For fuzz loops, PCOV is often 3–10× faster. If PCOV is unavailable for your PHP version, budget far more wall-clock time per iteration.
  • Non-determinism pollutes CI. Never put fuzz tests in the default pest run. A flaky fuzz failure at 2am on an unrelated PR erodes trust in the whole suite. Gate behind --group=fuzz or a FUZZ=true env var.
  • Side effects are landmines. Don't fuzz code that writes files, hits the network, dispatches jobs, or bootstraps the full Laravel container per iteration. If you must, wrap in DB::transaction() + rollback and use RefreshDatabase; even then, expect it to crawl.
  • State leakage across iterations. Fuzzers reuse the same PHP process. Static caches, singletons, and once()-memoized config persist between generated inputs. Reset anything stateful in the test body or a beforeEach.
  • Type-hint coverage matters. An untyped $input parameter typically yields strings only. If you want integers or arrays, declare them — don't coerce inside the test, or you're fuzzing your coercion, not your code.
  • Empty/short/edge strings dominate early runs. Most coverage-guided engines spend early iterations on trivial inputs. Give it more iterations than feels reasonable before concluding "it can't find bugs here".
  • Memory growth. Long runs with leaked objects (e.g., accumulating corpus in memory, closures held in arrays) hit memory_limit. Raise it for the fuzz job specifically (php -d memory_limit=1G vendor/bin/pest --group=fuzz).
  • assert() and expect() are your crash signal. A thrown exception is the normal failure mechanism; the fuzzer records the input. Use ->not->toThrow() deliberately for "should never throw" invariants, and plain assertions with precise messages for everything else.
  • Extension points to reach for: custom config (iterations, timeout, seed) via the published config; a fixed seed for reproducibility; and a committed corpus directory for incremental runs. Check vendor/jonpurvis/fuzz/src/ before writing your own generator — the mutators are usually the intended hook.
  • v1.1.0 is a skeleton/maintenance release. With only ~20 stars the surface area is small and docs are thin; read the source. Budget a half-day to wire it into CI properly the first time, and don't expect it to be a replacement for php-fuzzer or property-testing libraries — it's a lightweight Pest adapter for targeted invariants.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect