Skip to content

chore(deps): upgrade phpunit 12 -> 13#284

Merged
CybotTM merged 2 commits into
mainfrom
chore/upgrade-phpunit-13
May 8, 2026
Merged

chore(deps): upgrade phpunit 12 -> 13#284
CybotTM merged 2 commits into
mainfrom
chore/upgrade-phpunit-13

Conversation

@CybotTM
Copy link
Copy Markdown
Member

@CybotTM CybotTM commented May 6, 2026

Summary

  • Bumps phpunit/phpunit from ^12.3 to ^13.0 (resolves to 13.1.8)
  • Bumps brianium/paratest from ^7.11 to ^7.22 (paratest 7.21+ requires phpunit ^13)
  • phpstan/phpstan-phpunit stays at 2.0.16 (no upper bound on phpunit; already supports 13)
  • phpunit/php-code-coverage 12 -> 14 (transitive)
  • Updates the phpunit XML schema reference to 13.1 in phpunit.xml.dist and the three configs under config/testing/
  • Bumps the symfony phpunit-bridge SYMFONY_PHPUNIT_VERSION env var from 12.3 to 13.1 everywhere it is set
  • Regenerates phpstan-baseline.neon to absorb new errors from the upgraded phpstan-phpunit extension
  • config/reference.php is regenerated by symfony framework-bundle's post-install hook (triggered by the dependency tree update)

Breaking changes addressed

The PHPUnit 13 changelog removals were checked against the codebase; none of the removed APIs are used:

  • Assert::isType() - not used
  • assertContainsOnly() / assertNotContainsOnly() - not used (assertContainsOnlyInstancesOf(), which we do use, is unaffected)
  • containsOnly() - not used
  • #[RunClassInSeparateProcess] attribute - not used
  • any() matcher (hard-deprecated) - not used
  • Drop of PHP 8.3 support - not relevant (project requires PHP 8.5)

So no production or test code changes were required to keep the suite green.

phpstan baseline regeneration

phpstan/phpstan-phpunit reflects PHPUnit 13's stricter return types: MockBuilder::method()->with(...) without an explicit expects(...) now returns InvocationStubber, which has no with() method. This raises 121 new method.notFound and 121 new method.nonObject errors across the test suite.

The underlying runtime deprecation is "Using with*() without expects() is deprecated and will no longer be possible in PHPUnit 14" (introduced in PHPUnit 12.5.11). Migrating those 121 call sites to the expects()-based mock setup API is intentionally scoped out of this dependency bump - it's a substantial test refactor that should land as its own PR before the PHPUnit 14 upgrade. The baseline regeneration is consistent with the regeneration that landed in #282.

Test plan

  • make test passes locally with phpunit 13.1.8 (1923 tests, 5676 assertions, 0 errors, 0 failures; LDAP integration tests required the ldap-dev container to be reachable on the compose network)
  • docker compose run --rm app-dev php -d memory_limit=1G bin/phpstan analyze --no-progress reports [OK] No errors
  • composer update --ignore-platform-req=php phpunit/phpunit phpstan/phpstan-phpunit brianium/paratest symfony/phpunit-bridge --with-all-dependencies completes cleanly inside the dev container
  • CI green (test-unit, test-integration, e2e, codeql, scorecard)

Follow-ups (not in this PR)

  • Migrate the 143 tests that use with*() without expects() to the new mock builder API before bumping to PHPUnit 14
  • 6 PHP 8.5 deprecations in src/Service/ExportService.php (null array offsets) and src/Service/Util/TimeCalculationService.php (implicit float-to-int) surfaced by --display-deprecations; not new in this upgrade

Copilot AI review requested due to automatic review settings May 6, 2026 12:48
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR upgrades the test toolchain to PHPUnit 13 (and the required Paratest version), updating PHPUnit configuration files accordingly and refreshing generated artifacts impacted by the dependency graph change.

Changes:

  • Bump phpunit/phpunit to ^13.0 and brianium/paratest to ^7.22 (plus transitive PHPUnit ecosystem updates in composer.lock).
  • Update PHPUnit XML schema references and Symfony PHPUnit Bridge version pins to 13.1 in all PHPUnit configs.
  • Regenerate generated files impacted by the update (phpstan-baseline.neon, config/reference.php).

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
composer.json Raises PHPUnit and Paratest version constraints to allow PHPUnit 13.
composer.lock Updates locked dependency set for PHPUnit 13 / Paratest 7.22 and related transitive packages.
phpunit.xml.dist Updates PHPUnit schema URL and Symfony PHPUnit Bridge version pin.
config/testing/phpunit.xml.verbose Updates PHPUnit schema URL and Symfony PHPUnit Bridge version pin for verbose config.
config/testing/phpunit-performance.xml Updates PHPUnit schema URL and Symfony PHPUnit Bridge version pin for performance config.
config/testing/paratest.xml Updates PHPUnit schema URL and Symfony PHPUnit Bridge version pin for Paratest config.
phpstan-baseline.neon Refreshes baseline to account for new phpstan-phpunit/PHPUnit 13 typing diagnostics.
config/reference.php Regenerated Symfony reference config/types file due to dependency tree update.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.41%. Comparing base (dd64f00) to head (f6d936a).

Additional details and impacted files
@@             Coverage Diff              @@
##               main     #284      +/-   ##
============================================
- Coverage     81.47%   81.41%   -0.07%     
  Complexity     2584     2584              
============================================
  Files           172      172              
  Lines          7116     7085      -31     
============================================
- Hits           5798     5768      -30     
+ Misses         1318     1317       -1     
Flag Coverage Δ
integration 47.53% <ø> (+0.12%) ⬆️
unit 50.40% <ø> (-0.19%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request upgrades PHPUnit from version 12 to 13 and Paratest to version 7.22. The changes include updates to composer dependencies and the synchronization of various PHPUnit configuration files with the new version schema. Feedback on the PR highlights that the upgrade introduced a significant number of PHPStan errors related to mock objects; the reviewer suggests using targeted ignore rules in the main configuration file rather than expanding the baseline to avoid increasing technical debt.

Comment thread phpstan-baseline.neon Outdated
CybotTM added a commit that referenced this pull request May 8, 2026
… rule

Per gemini-code-assist review on PR #284: rather than adding 121 baseline
entries (242 errors total) for the PHPUnit 13 + phpstan-phpunit stub
mismatch on the legacy `->method('x')->with(...)->willReturn(...)` mock
chain, switch to two targeted message-restricted ignoreErrors rules in
phpstan.neon. Comment in-place documents the deprecation timeline (12.5.11
introduced, 14 removes) and that a dedicated test-modernization PR will
migrate ~143 call sites before the eventual PHPUnit 14 bump.

Net: phpstan-baseline.neon goes from 121 → 40 entries (242 → 80 errors).
The remaining 80 are not new — they're the same set PR #285 is clearing
in source; this PR will rebase against that once it merges and the
baseline should converge to zero plus the two scoped rules above.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
CybotTM added 2 commits May 8, 2026 11:32
Bumps phpunit/phpunit ^12.3 -> ^13.0 (resolves to 13.1.8) and the
required ecosystem packages:

- brianium/paratest ^7.11 -> ^7.22 (paratest 7.21+ requires phpunit ^13)
- phpstan/phpstan-phpunit stays at 2.0.16 (no upper bound; supports 13)
- phpunit/php-code-coverage 12 -> 14 (transitively)

Updates the phpunit XML schema reference to 13.1 and bumps the symfony
phpunit-bridge SYMFONY_PHPUNIT_VERSION env var from 12.3 to 13.1 across
all phpunit/paratest configuration files.

Regenerates phpstan-baseline.neon to absorb 121 new
"InvocationStubber::with()" errors raised by phpstan-phpunit's stricter
return-type definitions for mock builder calls in PHPUnit 13. The
underlying issue is the runtime deprecation
"Using with*() without expects() is deprecated and will no longer be
possible in PHPUnit 14" - migrating those 121 mock setups to the
expects()-based API is intentionally scoped out of this dependency bump
and is a follow-up before PHPUnit 14.

config/reference.php is regenerated by the symfony framework-bundle
post-install script triggered by the dependency tree update.

PHPUnit 13 removals (Assert::isType(), assertContainsOnly(),
containsOnly(), #[RunClassInSeparateProcess], any() matcher) are not
used by the codebase, so no test migrations are required.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
… rule

Per gemini-code-assist review on PR #284: rather than adding 121 baseline
entries (242 errors total) for the PHPUnit 13 + phpstan-phpunit stub
mismatch on the legacy `->method('x')->with(...)->willReturn(...)` mock
chain, switch to two targeted message-restricted ignoreErrors rules in
phpstan.neon. Comment in-place documents the deprecation timeline (12.5.11
introduced, 14 removes) and that a dedicated test-modernization PR will
migrate ~143 call sites before the eventual PHPUnit 14 bump.

Net: phpstan-baseline.neon goes from 121 → 40 entries (242 → 80 errors).
The remaining 80 are not new — they're the same set PR #285 is clearing
in source; this PR will rebase against that once it merges and the
baseline should converge to zero plus the two scoped rules above.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
@CybotTM CybotTM force-pushed the chore/upgrade-phpunit-13 branch from 1d416ae to f6d936a Compare May 8, 2026 09:34
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 8, 2026

@CybotTM CybotTM merged commit c7b139f into main May 8, 2026
23 of 24 checks passed
@CybotTM CybotTM deleted the chore/upgrade-phpunit-13 branch May 8, 2026 09:44
CybotTM added a commit that referenced this pull request May 8, 2026
…rvice stdClass narrowing

Resurfaced after rebasing onto main, where #284 added two ignoreErrors
patterns for InvocationStubber::with()/willReturn() that this branch's
mock-to-stub sweep already eliminated, and where #285's stronger Jira
return narrowing made four runtime assertions tautological.

  - JiraHttpClientService::getOAuthConsumerKey: drop ?? '' on
    ticketSystem->getLogin() — the entity setter is now string-typed, so
    the coalesce branch is unreachable.
  - JiraTicketServiceTest: drop assertInstanceOf(stdClass::class, ...)
    in createTicket / searchTickets / getTicket / addComment tests —
    the service signatures already declare : stdClass.
  - phpstan.neon: drop the InvocationStubber::with() and willReturn()
    on mixed ignore patterns — every with()-on-stub call site was
    converted to createStub(...)->method(...)->willReturn(...) in this
    branch's earlier mock-to-stub sweep.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
CybotTM added a commit that referenced this pull request May 8, 2026
…ne cleanup, fully-green PHPUnit (#288)

## Description

Comprehensive PHP modernization pass against the timetracker code base.
Brings the suite to a fully-green PHPUnit 12 state, eliminates 16
entries from the PHPStan baseline by fixing the underlying type issues,
adds `#[\SensitiveParameter]` to every credential-bearing method, and
aligns the Rector and PHP-CS-Fixer configs with the project's actual PHP
8.5 / Symfony 8 / PER-CS targets.

No production behaviour changes — every fix preserves runtime semantics;
only types, mocks and tooling configs move.

**Rebased onto `main` after #284 (PHPUnit 13 upgrade) and #285 (baseline
cleanup) merged.** All 5 commits carry `Signed-off-by:` (DCO).

## Type of Change

- [x] Code refactoring
- [x] Performance improvement (test suite — fewer mock allocations,
earlier PHPUnit 13 readiness)
- [ ] Breaking change

## Changes Made

### Production hardening
- **`#[\SensitiveParameter]` (PHP 8.2+)** — annotated 14 password /
token / OAuth-secret parameters across `LdapClientService::setUserPass`,
`ModernLdapService::{authenticate,validateInput}`,
`JiraOAuthApiService::{getFetchAccessTokenClient,getClient,fetchOAuthAccessToken,storeToken,getOAuthAuthUrl}`,
`TokenEncryptionService::{encryptToken,decryptToken,rotateToken}`, plus
`TicketSystem::setPassword` and
`UserTicketsystem::set{AccessToken,TokenSecret}`. Prevents secrets from
leaking into stack traces / Sentry / Monolog output.
- **Entity setter typing** — `TicketSystem`
`set{Name,Url,Login,Password,TicketUrl,BookTime}`,
`UserTicketsystem::set{AccessToken,TokenSecret}`, plus `User::$id`
(`?int`). Replaces the legacy untyped Doctrine getters/setters that
bypassed strict typing at call sites.
- **Jira service return narrowing** —
`JiraTicketService::{createTicket,searchTickets,getTicket,updateTicket,addComment}`
previously declared `: mixed`/`: object`; now return `: stdClass` (the
truthful runtime type from `json_decode($body, false)`).
- **`JiraWorkLogPayloadDto`** (`final readonly`, namespace
`App\DTO\Jira`) replaces `array $data`/`: mixed` in
`JiraWorkLogService::{prepareWorkLogData,createWorkLog,updateWorkLog}`
with a typed payload + serializer; removes the now-dead `is_object()`
guard in `updateEntryWorkLog`.
- **`OptimizedEntryRepository::findByFilterArrayOptimized`** — promoted
`array $filter` to a precise PHPDoc array-shape (`{user_id?: int,
customer_id?: int, ...}`) so PHPStan can verify each downstream
`ArrayTypeHelper::getInt` call.
- **Two genuine bugs unmasked by tightened types** — removed dead `null
=== $ticketUrl` branch in `Entry::getTicketSystemIssueLink` and dead `??
''` on `JiraHttpClientService::getOAuthConsumerKey` (the entity setter
is now string-typed, so the coalesce branch is unreachable).
- **Deprecation cleanup** —
`ExportService::getEntries{Ticket,Worklog}Url` no longer uses null as an
array-offset (cache key now `getId() ?? ''`);
`TimeCalculationService::formatDuration` swaps an implicit float `%` int
for `(int) floor(fmod(...))`.

### PHPUnit 12/13 cleanup
| Before | After |
|---|---|
| 1 error | **0** |
| 14 warnings | **0** |
| 6 deprecations | **0** |
| 495 notices (1698 occurrences) | **0** |
| 10 risky | **0** |

- **`createMock` → `createStub`** for 1698 sites across 29 test files
where no expectations are set (PHPUnit 12 distinguishes mocks from
stubs). 19 classes received `#[AllowMockObjectsWithoutExpectations]` at
class level instead, where the same fixture is reused with mixed
semantics across many tests.
- **Stripped 67 deprecated `->with(...)`** calls from stub-style chains
(deprecated in 12, hard error in 14). Removes the two
`InvocationStubber::with()` / `willReturn() on mixed` ignore patterns
from `phpstan.neon` introduced by #284 — every `with()`-on-stub call
site is now properly migrated.
- Converted `$this->createStub(...)` → `self::createStub(...)` to
satisfy PHPStan's static-call requirement.
- **Performance test cleanup** — replaced naive `unlink + rmdir` with
`Symfony\Filesystem::remove()`; clears 14 `Is a directory` / `Directory
not empty` warnings.
- **Exception-handler restoration** — added `tearDown {
restore_exception_handler(); parent::tearDown(); }` to
`EntryRepositoryExportTest`, balancing Symfony's `bootKernel()`
registration; clears 4 risky markers.
- **Real assertion** —
`EntryRepositoryFullIntegrationTest::testFindOverlappingEntriesExcludesSpecifiedId`
now uses a single `assertNotContains` instead of an
empty-result-tolerant foreach.
- **JiraOAuthApiService early-return tests** — dropped the
`expectNotToPerformAssertions()` calls; the existing `mock->method(...)`
calls are themselves the verifications PHPUnit was counting.
- Deleted redundant
`EntryTest::testGetTicketSystemIssueLinkReturnsTicketWhenNullTicketUrl`
(sibling empty-string test already covers the same intent under the
now-non-nullable `getTicketUrl(): string` contract).
- Removed 4 tautological `assertInstanceOf(stdClass::class, $result)`
calls in `JiraTicketServiceTest` since the service now declares `:
stdClass`.

### Tooling alignment
- **Rector** — bumped `LevelSetList::UP_TO_PHP_84` → `UP_TO_PHP_85`,
dropped `SymfonySetList::SYMFONY_73` (per-version sets are `@deprecated`
upstream) in favour of `->withComposerBased(symfony: true)` which
auto-detects the installed Symfony 8 line, and added
`->withAttributesSets(symfony: true)` so future annotation→attribute
migrations run automatically. Applied the one finding the bump surfaced
(`array_values($x)[0]` → `array_first($x)` in `SaveContractAction.php`).
- **PHP-CS-Fixer** — bumped `@PHP84Migration` → `@PHP85Migration`.
Codebase was already 8.5-clean: zero diff from the bump itself.
- **Stale config cleanup** — deleted 4 unreferenced files (root
`rector.php` placeholder,
`config/quality/{phpstan.dist.neon,phpstan-baseline.neon,phpstan-phpat.neon}`)
plus 90 orphaned PHPStan cache files under `config/quality/var/`
(already in `.gitignore` line 62, so they shouldn't have been tracked).
Verified no hits in CI/Makefile/composer for the deleted files.

## Testing

- [x] **`make test`** (Docker) — `Tests: 1922, Assertions: 5501,
Skipped: 1`, 0 errors / 0 failures / 0 warnings / 0 deprecations / 0
notices / 0 risky (the 1 skip is intentional and pre-existing).
- [x] **`composer check:all`** — PHPStan level 10 + bleedingEdge `[OK]
No errors` (311 files); PHPat `[OK] No errors`; PHP-CS-Fixer clean; Twig
lint clean.
- [x] **`bin/rector … --dry-run`** — clean.
- [x] **`composer security-check`** — no advisories.
- [x] **`npm run build`** — webpack compiled successfully, no warnings,
2106 assets emitted.
- [x] **`make e2e`** (Playwright on ephemeral compose stack) — **118
passed, 1 failed, 2 skipped**. The single failing test
(`settings.spec.ts:142 › should save show_empty_line setting`) is a
parallel-state flake: it passes 3/3 in isolation; the failure is a race
when the full suite runs concurrently. Not a regression caused by this
branch.
- [x] Manual testing completed.

## Code Quality

- [x] Code follows project coding standards (`@PER-CS:risky` +
`@PHP85Migration`)
- [x] Self-review completed
- [ ] Documentation updated — none needed; all changes are internal
types and tooling
- [x] No breaking changes — public API is unchanged; the `: stdClass`
narrowing on `JiraTicketService` is a covariant tightening (callers
already accessed `$result->key`/`$result->total` style on `stdClass`)

## Migration Notes

- **`composer rector` cannot be safely tested via `composer rector --
--dry-run`** — composer doesn't forward flags through that script, so
the dry-run flag is silently dropped and rector runs in apply-mode. Use
`bin/rector process src --config=config/quality/rector.php --dry-run`
directly when previewing.
- **Tests must run inside the Docker `app-dev` container** (per the
existing `make test` target). The host typically lacks `pdo_mysql` and
the `db_unittest` hostname only resolves on the compose network.
- **All 5 commits carry `Signed-off-by:` (DCO)**. The branch was rebased
onto `main` after #284 and #285 merged.

## Checklist

- [x] I have read the
[CONTRIBUTING](https://github.com/netresearch/timetracker/blob/main/CONTRIBUTING.md)
guidelines
- [x] I agree to follow the [Code of
Conduct](https://github.com/netresearch/timetracker/blob/main/CODE_OF_CONDUCT.md)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants