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

Pest Plugin Filesystem Laravel Package

ralphjsmit/pest-plugin-filesystem

Pest plugin that adds expressive filesystem assertions to your test suite, so you can verify files and directories exist, contain expected content, or are empty without verbose PHP boilerplate. A small, focused helper for cleaner Pest tests.

View on GitHub
Deep Wiki
Context7

Getting Started

Preconditions. This plugin only pays off in a Pest suite that asserts against the real local filesystem — generated files, exported CSVs, scaffolded directory trees, fixture copies. If everything you touch is already behind Storage::fake(), you get little from it. It requires PHP 8.1+ and was last released for Laravel 10 / Pest 2-era constraints.

Install.

composer require --dev ralphjsmit/pest-plugin-filesystem:^1.2

Pin to ^1.2 and commit the lockfile. Pest auto-discovers packages that declare extra.pest.plugins in their composer.json, so there is normally no tests/Pest.php registration step — but confirm rather than assume:

php artisan test --filter=SomeTrivialTest   # or ./vendor/bin/pest

If the expectation is "undefined", the plugin did not load and you should stop there.

Where to look first. Read vendor/ralphjsmit/pest-plugin-filesystem/src/ before writing a single assertion. The entire package is a handful of expect()->extend() calls wrapping file_exists(), is_dir(), file_get_contents() and friends — five minutes of reading tells you the exact assertion names, whether "contains" means substring or exact match, and how it handles trailing slashes. Then read its composer.json for the real pestphp/pest, phpunit/phpunit, and php constraints; the release notes are one line long and tell you nothing.

First use case. The canonical fit is a generator/export command:

it('exports the report', function () {
    $this->artisan('reports:export', ['--path' => $path = tmp_path('report.csv')]);

    expect($path)->toBeFile();
});

Assertion names as documented include toBeFile(); directory and content expectations exist under names you should confirm in the source (toBeDirectory(), toBeFileWithContent(), and similar) before standardising on them.

Implementation Patterns

Use it as the assertion layer, not the arrangement layer. Create files with File::put(), Storage::fake(), $this->artisan(...), or tempnam(); use the plugin only for the expect() half. Mixing the plugin into setup code muddies the failure output.

Give every test its own path. Fixed temp paths collide under --parallel/Paratest and produce flaky failures that cost more time than the readability gains.

beforeEach(function () {
    $this->tmp = sys_get_temp_dir() . '/pest-fs-' . Str::random(12);
    mkdir($this->tmp, 0777, true);
});

afterEach(function () {
    File::deleteDirectory($this->tmp);
});

Pest runs afterEach even when the test fails, which is the behaviour you want — confirm it against your Pest version rather than trusting it blindly.

Wrap the plugin behind your own expectations. This is the single highest-value pattern for a 3-star, single-release dependency: it gives you one call site to change if constraints break at the next Pest major, and it lets you adopt the plugin and the local-shim fallback interchangeably.

// tests/Pest.php
expect()->extend('toHaveGenerated', function (string $name) {
    return $this->toBeDirectory()
        ->and("{$this->value}/{$name}")->toBeFile();
});

If Pest 3 locks you out, reimplement the two or three assertions you actually use in the same file — the source is MIT and roughly an afternoon's work.

Assert the negative case in at least one test per suite. The failure mode that matters is a helper that passes when it should fail. Write one ->not->toBeFile() against a path you know is absent, and one content assertion against deliberately wrong content, before you trust the rest.

Prefer exactness where you can get it. For content checks, compare against a fully-known fixture string rather than a fragment, so line-ending and trailing-newline changes surface as test failures instead of silent passes.

Keep one idiom per repository. Pick either the plugin or raw file_exists()/File::exists() and write it into a short style note. A split codebase erases the expressiveness benefit that justified the dependency.

Gotchas and Tips

Check resolution before you fall in love with it. composer why-not ralphjsmit/pest-plugin-filesystem 1.2 tells you exactly what blocks the install. On Laravel 11/12, PHP 8.3+, or Pest 3, expect this to be the answer — the last release was February 2023 and exists solely to add Laravel 10 support.

It asserts on real paths, not Flysystem disks. Storage::fake('local') redirects writes to storage/framework/testing/disks/local; you would have to feed Storage::disk('local')->path(...) into the expectation. That works, but Laravel's built-in Storage::disk()->assertExists() is often the better fit — treat the plugin as complementary to Storage::fake(), not a replacement.

Content semantics are unverified. Whether "contains" is a substring match or something stricter determines whether expect($log)->toHaveContent('ERROR') can match NOTERROR in a line. Read the source; do not infer from the name.

Path separators and symlinks. If CI includes a Windows runner, exercise one path built with DIRECTORY_SEPARATOR mismatches and one symlinked fixture. These are the divergence points that pass locally and fail in CI.

Parallelism is the scaling risk, not speed. The helpers wrap native calls, so wall-clock cost is nil. Non-unique temp directories under Paratest are the realistic failure mode — randomise path segments and never reuse a hardcoded /tmp/test-output.

Cleanup must be unconditional. A try/finally or afterEach that runs on failure keeps long-lived CI runners and developer machines from filling with orphaned trees. Prefer File::deleteDirectory() over rm -rf shells.

Supply chain applies to dev dependencies. Test code runs in CI with repo secrets in scope, so pin the version, review the diff on any bump, and make sure composer audit covers dev requirements. Three stars, zero dependents, one maintainer, no security policy — the containment argument (test-only, removable in an afternoon) is what makes this acceptable, not the package's maturity.

Plan the exit at adoption time. Record in your dependency log that the fallback is inlining the needed expectations into tests/Pest.php under the MIT licence. If the package still has no release at the next dependency review, take the shim rather than carrying the dependency forward.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource