Test suite: unblock Laravel 12 & 13 (explicit 9-13 CI matrix) - #1084
Merged
Conversation
No behaviour change.
…s Laravel versions
Codebase uses mb_trim()/mb_ltrim()/mb_rtrim(), which are native only from PHP 8.4. On PHP 8.1-8.3 they come from symfony/polyfill-mbstring, which added them in v1.31.0.
There was a problem hiding this comment.
Pull request overview
Updates Scribe’s test infrastructure to validate Laravel 9–13 across PHPUnit 9–12 without changing package behavior.
Changes:
- Adds an explicit Laravel/PHP CI matrix and removes obsolete lowest-dependency manifests.
- Modernizes PHPUnit discovery, providers, configuration, and array-subset assertions.
- Normalizes version-dependent Testbench and paginator expectations.
Reviewed changes
Copilot reviewed 52 out of 54 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
.github/copilot-instructions.md |
Documents the new CI matrix. |
.github/workflows/lint.yml |
Improves Composer cache isolation. |
.github/workflows/run-tests.yml |
Adds explicit Laravel 9–13 jobs. |
.gitattributes |
Removes deleted lowest-manifest exports. |
.gitignore |
Ignores the PHPUnit cache directory. |
AGENTS.md |
Updates testing and Pint guidance. |
composer.json |
Removes DMS asserts and adds the mbstring polyfill. |
composer.lowest.json |
Removes the obsolete lowest manifest. |
composer.lowest.lock |
Removes the obsolete lowest lockfile. |
phpunit.xml |
Migrates configuration to PHPUnit 12. |
pint.json |
Preserves the subset trait’s loose comparison. |
tests/ArraySubsetAsserts.php |
Implements compatible subset assertions. |
tests/BaseLaravelTest.php |
Stabilizes Testbench filesystem defaults. |
tests/BaseUnitTest.php |
Uses the local assertion trait. |
tests/TestHelpers.php |
Adds version-neutral pagination metadata. |
tests/GenerateDocumentation/BehavioursTest.php |
Modernizes discovery and teardown. |
tests/GenerateDocumentation/OutputTest.php |
Adapts output paths and teardown. |
tests/Strategies/BodyParameters/GetFromBodyParamAttributeTest.php |
Migrates test discovery and assertions. |
tests/Strategies/BodyParameters/GetFromBodyParamTagTest.php |
Migrates test discovery and assertions. |
tests/Strategies/GetFromFormRequestTest.php |
Migrates test discovery and assertions. |
tests/Strategies/GetFromInlineValidatorTest.php |
Migrates test discovery and assertions. |
tests/Strategies/Headers/GetFromHeaderAttributeTest.php |
Migrates test discovery and assertions. |
tests/Strategies/Headers/GetFromHeaderTagTest.php |
Migrates test discovery and assertions. |
tests/Strategies/Metadata/GetFromDocBlocksTest.php |
Migrates test discovery and assertions. |
tests/Strategies/Metadata/GetFromMetadataAttributesTest.php |
Migrates test discovery and assertions. |
tests/Strategies/QueryParameters/GetFromQueryParamAttributeTest.php |
Migrates test discovery and assertions. |
tests/Strategies/QueryParameters/GetFromQueryParamTagTest.php |
Migrates test discovery and assertions. |
tests/Strategies/ResponseFields/GetFromResponseFieldAttributesTest.php |
Migrates test discovery and assertions. |
tests/Strategies/ResponseFields/GetFromResponseFieldTagTest.php |
Migrates test discovery and assertions. |
tests/Strategies/Responses/ResponseCallsTest.php |
Migrates test discovery. |
tests/Strategies/Responses/UseApiResourceTagsTest.php |
Handles version-dependent pagination metadata. |
tests/Strategies/Responses/UseResponseAttributesTest.php |
Adds PHPUnit-compatible inline datasets. |
tests/Strategies/Responses/UseResponseFileTagTest.php |
Adds compatible data-provider metadata. |
tests/Strategies/Responses/UseResponseTagTest.php |
Adds compatible data-provider metadata. |
tests/Strategies/Responses/UseTransformerTagsTest.php |
Adds compatible data-provider metadata. |
tests/Strategies/UrlParameters/GetFromLaravelAPITest.php |
Migrates test discovery and assertions. |
tests/Strategies/UrlParameters/GetFromUrlParamAttributeTest.php |
Migrates test discovery and assertions. |
tests/Strategies/UrlParameters/GetFromUrlParamTagTest.php |
Migrates test discovery and assertions. |
tests/Unit/AfterExtractingHookTest.php |
Migrates test discovery. |
tests/Unit/AnnotationParserTest.php |
Adds compatible data-provider metadata. |
tests/Unit/ConfigDifferTest.php |
Migrates test discovery. |
tests/Unit/ExtractedEndpointDataTest.php |
Migrates test discovery. |
tests/Unit/ExtractorStrategiesInvocationTest.php |
Modernizes discovery and providers. |
tests/Unit/ExtractorTest.php |
Modernizes discovery and providers. |
tests/Unit/HtmlWriterTest.php |
Migrates test discovery. |
tests/Unit/OpenAPISpecWriterTest.php |
Migrates test discovery. |
tests/Unit/OutputEndpointDataTest.php |
Migrates test discovery. |
tests/Unit/PathConfigurationTest.php |
Migrates test discovery. |
tests/Unit/PostmanCollectionWriterTest.php |
Migrates test discovery. |
tests/Unit/RouteMatcherTest.php |
Migrates test discovery. |
tests/Unit/RoutePatternMatcherTest.php |
Migrates test discovery. |
tests/Unit/UtilsTest.php |
Migrates test discovery. |
tests/Unit/ValidationRuleParsingTest.php |
Modernizes discovery and providers. |
tests/Unit/WritingUtilsTest.php |
Migrates test discovery. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
While looking into what it would take to support Laravel 13's new
JsonApiResource, I ran into a more basic problem: there'd be no way to prove such a thing worked, because the test suite cannot resolve to anything above Laravel 11. That seemed worth fixing first, so this PR is only about the test infrastructure — the suite now genuinely runs on Laravel 9 through 13.No behaviour change —
src/andcamel/are untouched. Every failure found on Laravel 12/13 turned out to be a stale expectation in the tests or a testbench default, not a regression in the package.The problem
composer.jsonalready advertiseslaravel/framework: … || ^13.0,orchestra/testbench: … || ^11.0,pest: … || ^4.0andphpunit: … || ^12.0. But a single dev dependency held the whole tree down:So every
highestCI job resolved to Laravel 11 at best, and the declared support for 12 and 13 was never exercised.The matrix compounded this: it varied only PHP (8.1–8.4) and let the resolver pick the framework. That is not a Laravel matrix — on PHP 8.1
highestlands on Laravel 10, on PHP 8.2 it jumps straight to 12, so Laravel 11 would have dropped out of coverage entirely once the blocker was removed, silently.Constraints I worked within
requiredeclaresphp: >=8.1, and I'm not changing that here. On PHP 8.1 the dev tree resolves to Pest 1.23 / PHPUnit 9.6 — with Laravel 9 and with Laravel 10 alike — and PHPUnit 9 does not read attributes. Pest 2 can't be forced there either: the resolver rejects the whole 2.x range on PHP 8.1 (the latest release requires PHP^8.2, a band in the middle pullsbrianium/paratest ^7.4which needs ≥8.2, and the rest hit PHPUnit conflicts). At the other end, PHPUnit 12 no longer reads docblock metadata at all.That single fact — one supported PHP version forcing PHPUnit 9 while another forces PHPUnit 12 — is the source of most of the compromises below. They're all listed under Future cleanup below so they can be removed when the floors move.
Keeping Laravel 9 turned out to be nearly free, incidentally: it resolves from the main manifest to the same pest 1.23 / phpunit 9.6 pair as PHP 8.1 + Laravel 10, and the suite is green on it. It costs one matrix row and no extra concessions.
Changes
Dropping
dms/phpunit-arraysubset-assertsReplaced by
tests/ArraySubsetAsserts.php, a ~40-line trait reproducing the package's semantics. All 127 call sites stay exactly as they are; only the import changes, in the 19 files that carry it.The subtlety worth flagging in review: the obvious implementation (
array_replace_recursive()thenassertEquals()) is stricter than the original. dms compares with==, where[] == nullholds;assertEquals([], null)fails on the type mismatch. The difference surfaces in exactly one test (GetFromFormRequestTest, anobject-typed field with anexample). The trait keeps the loose comparison and only reachesassertEquals()to render a diff once the arrays are known to differ. It is excluded from Pint, becausestrict_comparisonwould rewrite that comparison and quietly change what the assertions accept.PHPUnit 9 → 12 compatibility
/** @test */annotations across 39 files replaced by thetest_*method prefix. This is the version-neutral form — the one laravel/framework itself uses — and needs no duplication:/** @test */is invisible to PHPUnit 12,#[Test]is invisible to PHPUnit 9.@dataProviderworks up to PHPUnit 11,#[DataProvider]from 10. The two overlap only on 10 and 11, so both markups sit side by side on all 8 providers, plus the one@testWithblock inUseResponseAttributesTest(its two rows become two#[TestWith]attributes). Verified that PHPUnit 11 does not duplicate the data set or warn — the attribute wins. (The providers were alreadystatic, so nothing to change there.)tests/Fixturesand the test controllers —@group,@bodyParam,@response— are input to Scribe's own extractor, not PHPUnit metadata, and are deliberately left alone. An automated annotation→attribute conversion breaks 7 tests by rewriting@group.phpunit.xmlmigrated with--migrate-configuration: schema 12.5,<source>instead of<coverage>,backupStaticProperties,cacheDirectory, obsoleteconvert*ToExceptionsremoved. Verified that PHPUnit 9 still accepts this file and still discovers the tests, so no second config is needed for the lower rows.tearDown()overrides inBehavioursTestandOutputTestnow callparent::tearDown(). This also clears 32 risky tests ("Test code or tested code did not remove its own error handlers", flagged by default from PHPUnit 10): without the parent call, Testbench never got to restore the handlers it installed.CI matrix (
.github/workflows/run-tests.yml)The framework is now pinned per job with
composer update --with="laravel/framework:…"— preferred overcomposer require --no-updatebecause it doesn't mutatecomposer.jsonin the working copy:The
if:-guarded duplicate install/execute steps collapse into one.About the
^9.41floor:composer.jsonallows^9.21, but the suite itself usesRule::enum(), which only exists from 9.41.0. That's a pre-existing gap between what the package claims and what the tests can actually cover — I've pinned to what runs rather than silently widening or narrowing declared support.Removing
composer.lowest.json/composer.lowest.lockcomposer update --prefer-lowest --prefer-stable --with="laravel/framework:^9.41"does the same job without a second manifest. The old one had already drifted:erusev/parsedown 1.7.4where the real manifest hasparsedown/parsedown ^1.7, a strayspatie/data-transfer-object(previously removed), and nopint,phpstanorray. The committed lockfile bought nothing either — CI rancomposer updateover it anyway, andcomposer.lockis gitignored. That's ~8.5k lines of lockfile deleted.Version-neutral assertions
Three distinct causes behind 27 failures on Laravel 12 and 9 on Laravel 13:
filesystems.disks.local.serve = true(testbench 11 ships itfalse). When on, Laravel registers aGET storage/{path}route, Scribe documents it, and every group count and byte-for-byte output comparison shifts.BaseLaravelTestnow pins it tofalseso the suite doesn't depend on the testbench default.OutputTestexpectedstorage/app/scribe/in the console output, but the testbench 10+ skeleton points thelocaldisk atstorage/app/private.storageOutputPath()now derives the expected path from the disk instead of hardcoding it — hardcoding the new path would just break Laravel 10 in the other direction.current_page_urlwas added to the paginator'smetaduring the 12.x branch (11.55 doesn't have it).TestHelpers::paginationMeta()detects it from the paginator itself rather than from a version number, since it landed in a patch release.symfony/polyfill-mbstringadded torequireThe one production-facing change. The codebase already calls
mb_trim()/mb_ltrim()/mb_rtrim(), native only from PHP 8.4; on 8.1–8.3 they come from the polyfill (v1.31.0+). It was previously present only transitively, so the lowest-deps job was one dependency change away from a fatal error.Docs
AGENTS.mdand.github/copilot-instructions.mddescribed PHP-CS-Fixer (the project actually uses Pint), the@testannotation, and the old PHP-only matrix. All three updated.Out of scope
The suite now reports
Tests: 329, Assertions: 988, Deprecations: 6on Laravel 13 / PHPUnit 12 — no failures, no risky tests. The remaining 6 deprecations all come frommpociot/reflection-docblock(Tag.php, implicitly nullable parameters). That dependency is unmaintained and will keep emitting these on PHP 8.4+, so it needs a fix or a replacement rather than a patch here. Separate issue.Future cleanup
Everything below exists only to support the lower end of the supported range. When the floors move:
When PHP 8.1 is dropped (PHPUnit 9 goes with it — this is the big one):
@dataProvider/@testWithdocblocks; the#[DataProvider]/#[TestWith]attributes are then sufficient.composer.json(^1.21,^9.0) can be raised.test_*prefix can stay as is — it's neutral, not a workaround.When Laravel 9 is dropped:
^9.41CI floor and its explanatory comment go with the Laravel 9 rows.When the floor reaches Laravel 12.65 / 13 (min testbench 11):
filesystems.disks.local.servepin inBaseLaravelTestbecomes unnecessary.TestHelpers::paginationMeta()can go —current_page_urlis always present, and themetablocks can be inlined again.