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

Placeholder Phpunit Laravel Package

php-extended/placeholder-phpunit

A placeholder package for PHPUnit in php-extended projects. It provides minimal stubs so libraries can reference PHPUnit types without a hard dependency, keeping production installs lean while remaining compatible when PHPUnit is present.

View on GitHub
Deep Wiki
Context7

Getting Started

php-extended/placeholder-phpunit is a stubbed mirror of PHPUnit's public API surface (PHPUnit\Framework\TestCase, Assert, Constraint, mock-object types, exceptions, attributes) shipped as a plain Composer package with zero transitive dependencies. It exists so a library can declare PHPUnit-facing APIs — type hints, extends, implements — without forcing phpunit/phpunit onto every consumer.

Minimal steps:

  1. Install as a dev or peer dependency:
    composer require --dev php-extended/placeholder-phpunit
    
  2. Read vendor/php-extended/placeholder-phpunit/composer.json first — the version and require.php will tell you which PHPUnit API generation the stubs model. Then skim vendor/php-extended/placeholder-phpunit/src/ to confirm the symbols you need exist (Framework/TestCase.php, Framework/Constraint/, Framework/MockObject/, Framework/Attributes/).
  3. First use case: you maintain a library that offers an optional PHPUnit integration — a custom Constraint class, a TestCase trait, a listener, a data-provider helper. You want composer install to succeed and IDE/static-analysis to resolve your type hints even when PHPUnit is absent.

The first thing to write is almost always a class that extends or implements a PHPUnit symbol:

namespace MyLib\Testing;

use PHPUnit\Framework\Constraint\Constraint;

final class IsValidTokenConstraint extends Constraint
{
    public function toString(): string { return 'is a valid token'; }
    protected function matches($other): bool { return (bool) preg_match('/^[a-f0-9]{32}$/', $other); }
}

With the placeholder on the autoload path this file parses and analyzes; without PHPUnit installed it is never executed.

Implementation Patterns

Optional integration + suggest. The canonical pattern: ship the PHPUnit-facing API in your library, declare the placeholder as a hard requirement (or dev, depending on your package), and tell consumers what to install to use it. Guard at runtime instead of assuming PHPUnit exists:

{
  "require": { "php-extended/placeholder-phpunit": "*" },
  "suggest": {
    "phpunit/phpunit": "Required to actually run the MyLib\\Testing constraints inside PHPUnit"
  }
}
if (!class_exists(\PHPUnit\Framework\TestCase::class) || !method_exists(\PHPUnit\Framework\Assert::class, 'assertThat')) {
    throw new \LogicException('Install phpunit/phpunit to use MyLib\\Testing.');
}

Static-analysis-only usage. For a tool that introspects PHPUnit test classes with php-parser, reflection, or Rector, the placeholder is a cheap way to give PHPStan/Psalm the symbol table without pulling in PHPUnit (and its PHP-version constraints) into a CLI tool's dependency graph. Add it to require-dev and point phpstan.neon at the vendor path if you want it treated as a stub rather than analyzed source.

Split CI matrices. Run one job with the placeholder installed (proves your library compiles standalone — no accidental PHPUnit coupling, no use of symbols your stub set doesn't cover) and a second job with real phpunit/phpunit (proves the integration actually works). The first job is the one this package uniquely enables; it will fail loudly if you reach for a PHPUnit class the stub set does not define, which is a useful "am I leaking PHPUnit into my core?" gate.

Extension points as the integration contract. Build your public surface around the interfaces PHPUnit itself exposes — Constraint, TestListener/TestHook, TestCase — rather than concrete assertion calls. Placeholder stubs cover the interfaces well; they are much thinner on the behavioral internals of TestCase, so interface-first design both survives the stubs and stays forward-compatible across PHPUnit majors.

Gotchas and Tips

  • The stubs are not executable. Method bodies are empty or throw. Never call anything at runtime without a class_exists/method_exists guard, and never let the placeholder be the only thing on the classpath in a test run.
  • Do not install placeholder + real PHPUnit in the same composer.json. Both declare PHPUnit\Framework\TestCase and friends, and Composer will happily install both — which of the two class files wins is determined by autoloader registration order, i.e. effectively arbitrary. Keep them in separate jobs, separate packages, or separate Composer roots.
  • API drift is real and silent. The stub set is a point-in-time copy. PHPUnit 10+ changed TestCase signatures, moved to attributes (#[DataProvider], #[Test]), dropped annotation support, and reshuffled the mock-object namespace. If your code compiles against the stubs but explodes under real PHPUnit, diff the stub's method signatures against your target PHPUnit version — that is the first place to look, not your code.
  • Namespaces are the trap, not the classes. A stub that lacks PHPUnit\Framework\Attributes\ will still autoload fine for everything else; you only find out when the attribute class 404s under PHPUnit 10. Grep src/ for every FQCN you reference before you trust the install.
  • replace is not a substitute. Some packages try "replace": {"phpunit/phpunit": "*"} to fake PHPUnit away. It pollutes the dependency graph and confuses Composer resolution; use a placeholder package like this one instead, scoped to require-dev where possible.
  • Debugging a "class not found" in CI: composer dump-autoload -o, then grep -i phpunit vendor/composer/autoload_classmap.php to see exactly which package won the FQCN. If the placeholder shows up in a job that intended to run real PHPUnit, your require-dev is wrong.
  • For PHPStan/Psalm-only needs, jetbrains/phpstorm-stubs already ships comprehensive PHPUnit stubs and is better maintained; reach for this package specifically when you need a Composer-installable autoload path (e.g. building docs, generating reflection data, or shipping a package whose classes extend PHPUnit types).
  • If you are an application, not a library, you do not need this package at all — composer require --dev phpunit/phpunit is the answer. This is a library-author's tool for keeping PHPUnit optional while still being type-safe.
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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment