From 11529a4c18ca2119dd7e9074edf2a9f0755c31f3 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 5 Feb 2026 09:37:45 +0100 Subject: [PATCH 1/4] [post] phpunit 12.5 in diffs --- composer.lock | 12 ++-- .../images/blog/2026/phpunit-notices-spam.png | Bin 0 -> 44241 bytes ...02-07-upgrade-to-phpunit-125-in-7-diffs.md | 58 ++++++++++++++++++ 3 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 public/assets/images/blog/2026/phpunit-notices-spam.png create mode 100644 resources/blog/posts/2026/2026-02-07-upgrade-to-phpunit-125-in-7-diffs.md diff --git a/composer.lock b/composer.lock index 3a4e7ab96..fecc2098a 100644 --- a/composer.lock +++ b/composer.lock @@ -7296,16 +7296,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "12.5.2", + "version": "12.5.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b" + "reference": "b015312f28dd75b75d3422ca37dff2cd1a565e8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4a9739b51cbcb355f6e95659612f92e282a7077b", - "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b015312f28dd75b75d3422ca37dff2cd1a565e8d", + "reference": "b015312f28dd75b75d3422ca37dff2cd1a565e8d", "shasum": "" }, "require": { @@ -7361,7 +7361,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.2" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.3" }, "funding": [ { @@ -7381,7 +7381,7 @@ "type": "tidelift" } ], - "time": "2025-12-24T07:03:04+00:00" + "time": "2026-02-06T06:01:44+00:00" }, { "name": "phpunit/php-file-iterator", diff --git a/public/assets/images/blog/2026/phpunit-notices-spam.png b/public/assets/images/blog/2026/phpunit-notices-spam.png new file mode 100644 index 0000000000000000000000000000000000000000..32a898806338fbe223997adfc5d16c45094de771 GIT binary patch literal 44241 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 + +There is much more complex definition in the PHPUnit docs, but in plain English: + +* **a mock** is a fake class, that has expectations of being called or not being called, + +```php +$someMock = $this->createMock(SomeClass::class); +$someMock->expects($this->once())->method('someMethod')->willReturn(100); +``` + +We expect the `someMethod` to be called, or PHPUnit will crash. + +* **a stub** is also a fake class, but it doesn't do anything at all. + +```php +$someClass = new SomeClass($this->createStub(SomeDependency::class)); +``` + +We can use it to make comply with constructor requirements + +```php +$request = $this->createStub(Request::class); +$requestStack = new RequestStack($request); + +$this->assertSame($request, $requestStack->getCurrentRequest()); +``` + +We can also use it to assert the same object is used on a getter call later. + +
+ +This leads us to first simplest change we can do. + +
+ +## 1. Use `createStub()` over `createMock()`, where no expectations + +```diff + +``` + + + + From 2e8b0ab115cd51331cbc526eb7d67e25beaf347e Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 6 Feb 2026 11:40:08 +0100 Subject: [PATCH 2/4] bump to PHPUnit 13 --- composer.json | 2 +- composer.lock | 504 +++++++++++------- rector.php | 1 + ...02-07-upgrade-to-phpunit-125-in-7-diffs.md | 423 ++++++++++++++- 4 files changed, 723 insertions(+), 207 deletions(-) diff --git a/composer.json b/composer.json index 6cfb8463d..eac8eb0d8 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "phpstan/extension-installer": "^1.4.3", "phpstan/phpstan": "^2.1.36", "phpstan/phpstan-webmozart-assert": "^2.0", - "phpunit/phpunit": "^12.5", + "phpunit/phpunit": "^13.0", "rector/jack": "^0.5.1", "rector/swiss-knife": "^2.3.3", "tomasvotruba/class-leak": "^2.1" diff --git a/composer.lock b/composer.lock index fecc2098a..e03667b11 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "02f9905904530e3a4e70255ba29b33fd", + "content-hash": "dce6e32d10485d72eba90cee236aeadd", "packages": [ { "name": "brick/math", @@ -7296,16 +7296,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "12.5.3", + "version": "13.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "b015312f28dd75b75d3422ca37dff2cd1a565e8d" + "reference": "a8b58fde2f4fbc69a064e1f80ff917607cf7737c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b015312f28dd75b75d3422ca37dff2cd1a565e8d", - "reference": "b015312f28dd75b75d3422ca37dff2cd1a565e8d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a8b58fde2f4fbc69a064e1f80ff917607cf7737c", + "reference": "a8b58fde2f4fbc69a064e1f80ff917607cf7737c", "shasum": "" }, "require": { @@ -7313,17 +7313,17 @@ "ext-libxml": "*", "ext-xmlwriter": "*", "nikic/php-parser": "^5.7.0", - "php": ">=8.3", - "phpunit/php-file-iterator": "^6.0", - "phpunit/php-text-template": "^5.0", - "sebastian/complexity": "^5.0", - "sebastian/environment": "^8.0.3", - "sebastian/lines-of-code": "^4.0", - "sebastian/version": "^6.0", + "php": ">=8.4", + "phpunit/php-file-iterator": "^7.0", + "phpunit/php-text-template": "^6.0", + "sebastian/complexity": "^6.0", + "sebastian/environment": "^9.0", + "sebastian/lines-of-code": "^5.0", + "sebastian/version": "^7.0", "theseer/tokenizer": "^2.0.1" }, "require-dev": { - "phpunit/phpunit": "^12.5.1" + "phpunit/phpunit": "^13.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -7332,7 +7332,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "12.5.x-dev" + "dev-main": "13.0.x-dev" } }, "autoload": { @@ -7361,7 +7361,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.3" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/13.0.1" }, "funding": [ { @@ -7381,32 +7381,32 @@ "type": "tidelift" } ], - "time": "2026-02-06T06:01:44+00:00" + "time": "2026-02-06T06:05:15+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "6.0.1", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5" + "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", - "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", + "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -7434,7 +7434,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/7.0.0" }, "funding": [ { @@ -7454,28 +7454,28 @@ "type": "tidelift" } ], - "time": "2026-02-02T14:04:18+00:00" + "time": "2026-02-06T04:33:26+00:00" }, { "name": "phpunit/php-invoker", - "version": "6.0.0", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" + "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", - "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", + "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "suggest": { "ext-pcntl": "*" @@ -7483,7 +7483,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -7510,40 +7510,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" + "source": "https://github.com/sebastianbergmann/php-invoker/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-invoker", + "type": "tidelift" } ], - "time": "2025-02-07T04:58:58+00:00" + "time": "2026-02-06T04:34:47+00:00" }, { "name": "phpunit/php-text-template", - "version": "5.0.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" + "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", - "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/a47af19f93f76aa3368303d752aa5272ca3299f4", + "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -7570,40 +7582,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template", + "type": "tidelift" } ], - "time": "2025-02-07T04:59:16+00:00" + "time": "2026-02-06T04:36:37+00:00" }, { "name": "phpunit/php-timer", - "version": "8.0.0", + "version": "9.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" + "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", - "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a0e12065831f6ab0d83120dc61513eb8d9a966f6", + "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "8.0-dev" + "dev-main": "9.0-dev" } }, "autoload": { @@ -7630,28 +7654,40 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" + "source": "https://github.com/sebastianbergmann/php-timer/tree/9.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-timer", + "type": "tidelift" } ], - "time": "2025-02-07T04:59:38+00:00" + "time": "2026-02-06T04:37:53+00:00" }, { "name": "phpunit/phpunit", - "version": "12.5.9", + "version": "13.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "83d4c158526c879b4c5cf7149d27958b6d912373" + "reference": "e045b31f855d4511a2091aa707a2bb0bb5456632" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/83d4c158526c879b4c5cf7149d27958b6d912373", - "reference": "83d4c158526c879b4c5cf7149d27958b6d912373", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e045b31f855d4511a2091aa707a2bb0bb5456632", + "reference": "e045b31f855d4511a2091aa707a2bb0bb5456632", "shasum": "" }, "require": { @@ -7664,22 +7700,22 @@ "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", - "php": ">=8.3", - "phpunit/php-code-coverage": "^12.5.2", - "phpunit/php-file-iterator": "^6.0.1", - "phpunit/php-invoker": "^6.0.0", - "phpunit/php-text-template": "^5.0.0", - "phpunit/php-timer": "^8.0.0", - "sebastian/cli-parser": "^4.2.0", - "sebastian/comparator": "^7.1.4", - "sebastian/diff": "^7.0.0", - "sebastian/environment": "^8.0.3", - "sebastian/exporter": "^7.0.2", - "sebastian/global-state": "^8.0.2", - "sebastian/object-enumerator": "^7.0.0", - "sebastian/recursion-context": "^7.0.1", - "sebastian/type": "^6.0.3", - "sebastian/version": "^6.0.0", + "php": ">=8.4.1", + "phpunit/php-code-coverage": "^13.0.0", + "phpunit/php-file-iterator": "^7.0.0", + "phpunit/php-invoker": "^7.0.0", + "phpunit/php-text-template": "^6.0.0", + "phpunit/php-timer": "^9.0.0", + "sebastian/cli-parser": "^5.0.0", + "sebastian/comparator": "^8.0.0", + "sebastian/diff": "^8.0.0", + "sebastian/environment": "^9.0.0", + "sebastian/exporter": "^8.0.0", + "sebastian/global-state": "^9.0.0", + "sebastian/object-enumerator": "^8.0.0", + "sebastian/recursion-context": "^8.0.0", + "sebastian/type": "^7.0.0", + "sebastian/version": "^7.0.0", "staabm/side-effects-detector": "^1.0.5" }, "bin": [ @@ -7688,7 +7724,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "12.5-dev" + "dev-main": "13.0-dev" } }, "autoload": { @@ -7720,7 +7756,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.9" + "source": "https://github.com/sebastianbergmann/phpunit/tree/13.0.0" }, "funding": [ { @@ -7744,7 +7780,7 @@ "type": "tidelift" } ], - "time": "2026-02-05T08:01:09+00:00" + "time": "2026-02-06T04:57:13+00:00" }, { "name": "rector/jack", @@ -7842,28 +7878,28 @@ }, { "name": "sebastian/cli-parser", - "version": "4.2.0", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04" + "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/90f41072d220e5c40df6e8635f5dafba2d9d4d04", - "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/48a4654fa5e48c1c81214e9930048a572d4b23ca", + "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.2-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -7887,7 +7923,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.0" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/5.0.0" }, "funding": [ { @@ -7907,31 +7943,31 @@ "type": "tidelift" } ], - "time": "2025-09-14T09:36:45+00:00" + "time": "2026-02-06T04:39:44+00:00" }, { "name": "sebastian/comparator", - "version": "7.1.4", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "6a7de5df2e094f9a80b40a522391a7e6022df5f6" + "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a7de5df2e094f9a80b40a522391a7e6022df5f6", - "reference": "6a7de5df2e094f9a80b40a522391a7e6022df5f6", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/29b232ddc29c2b114c0358c69b3084e7c3da0d58", + "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58", "shasum": "" }, "require": { "ext-dom": "*", "ext-mbstring": "*", - "php": ">=8.3", - "sebastian/diff": "^7.0", - "sebastian/exporter": "^7.0" + "php": ">=8.4", + "sebastian/diff": "^8.0", + "sebastian/exporter": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^12.2" + "phpunit/phpunit": "^13.0" }, "suggest": { "ext-bcmath": "For comparing BcMath\\Number objects" @@ -7939,7 +7975,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "7.1-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -7979,7 +8015,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.4" + "source": "https://github.com/sebastianbergmann/comparator/tree/8.0.0" }, "funding": [ { @@ -7999,33 +8035,33 @@ "type": "tidelift" } ], - "time": "2026-01-24T09:28:48+00:00" + "time": "2026-02-06T04:40:39+00:00" }, { "name": "sebastian/complexity", - "version": "5.0.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" + "reference": "c5651c795c98093480df79350cb050813fc7a2f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", - "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c5651c795c98093480df79350cb050813fc7a2f3", + "reference": "c5651c795c98093480df79350cb050813fc7a2f3", "shasum": "" }, "require": { "nikic/php-parser": "^5.0", - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -8049,41 +8085,53 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" + "source": "https://github.com/sebastianbergmann/complexity/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/complexity", + "type": "tidelift" } ], - "time": "2025-02-07T04:55:25+00:00" + "time": "2026-02-06T04:41:32+00:00" }, { "name": "sebastian/diff", - "version": "7.0.0", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "7ab1ea946c012266ca32390913653d844ecd085f" + "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", - "reference": "7ab1ea946c012266ca32390913653d844ecd085f", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/a2b6d09d7729ee87d605a439469f9dcc39be5ea3", + "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0", + "phpunit/phpunit": "^13.0", "symfony/process": "^7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -8116,35 +8164,47 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" + "source": "https://github.com/sebastianbergmann/diff/tree/8.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/diff", + "type": "tidelift" } ], - "time": "2025-02-07T04:55:46+00:00" + "time": "2026-02-06T04:42:27+00:00" }, { "name": "sebastian/environment", - "version": "8.0.3", + "version": "9.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68" + "reference": "bb64d08145b021b67d5f253308a498b73ab0461e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68", - "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/bb64d08145b021b67d5f253308a498b73ab0461e", + "reference": "bb64d08145b021b67d5f253308a498b73ab0461e", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "suggest": { "ext-posix": "*" @@ -8152,7 +8212,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "8.0-dev" + "dev-main": "9.0-dev" } }, "autoload": { @@ -8180,7 +8240,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3" + "source": "https://github.com/sebastianbergmann/environment/tree/9.0.0" }, "funding": [ { @@ -8200,34 +8260,34 @@ "type": "tidelift" } ], - "time": "2025-08-12T14:11:56+00:00" + "time": "2026-02-06T04:43:29+00:00" }, { "name": "sebastian/exporter", - "version": "7.0.2", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "016951ae10980765e4e7aee491eb288c64e505b7" + "reference": "dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/016951ae10980765e4e7aee491eb288c64e505b7", - "reference": "016951ae10980765e4e7aee491eb288c64e505b7", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea", + "reference": "dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=8.3", - "sebastian/recursion-context": "^7.0" + "php": ">=8.4", + "sebastian/recursion-context": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -8270,7 +8330,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.2" + "source": "https://github.com/sebastianbergmann/exporter/tree/8.0.0" }, "funding": [ { @@ -8290,35 +8350,35 @@ "type": "tidelift" } ], - "time": "2025-09-24T06:16:11+00:00" + "time": "2026-02-06T04:44:28+00:00" }, { "name": "sebastian/global-state", - "version": "8.0.2", + "version": "9.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "ef1377171613d09edd25b7816f05be8313f9115d" + "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d", - "reference": "ef1377171613d09edd25b7816f05be8313f9115d", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e52e3dc22441e6218c710afe72c3042f8fc41ea7", + "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7", "shasum": "" }, "require": { - "php": ">=8.3", - "sebastian/object-reflector": "^5.0", - "sebastian/recursion-context": "^7.0" + "php": ">=8.4", + "sebastian/object-reflector": "^6.0", + "sebastian/recursion-context": "^8.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "8.0-dev" + "dev-main": "9.0-dev" } }, "autoload": { @@ -8344,7 +8404,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/9.0.0" }, "funding": [ { @@ -8364,33 +8424,33 @@ "type": "tidelift" } ], - "time": "2025-08-29T11:29:25+00:00" + "time": "2026-02-06T04:45:13+00:00" }, { "name": "sebastian/lines-of-code", - "version": "4.0.0", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f" + "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f", - "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", + "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", "shasum": "" }, "require": { "nikic/php-parser": "^5.0", - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -8414,42 +8474,54 @@ "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.0" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/5.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code", + "type": "tidelift" } ], - "time": "2025-02-07T04:57:28+00:00" + "time": "2026-02-06T04:45:54+00:00" }, { "name": "sebastian/object-enumerator", - "version": "7.0.0", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" + "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", - "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", + "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", "shasum": "" }, "require": { - "php": ">=8.3", - "sebastian/object-reflector": "^5.0", - "sebastian/recursion-context": "^7.0" + "php": ">=8.4", + "sebastian/object-reflector": "^6.0", + "sebastian/recursion-context": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -8472,40 +8544,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/8.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/object-enumerator", + "type": "tidelift" } ], - "time": "2025-02-07T04:57:48+00:00" + "time": "2026-02-06T04:46:36+00:00" }, { "name": "sebastian/object-reflector", - "version": "5.0.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "4bfa827c969c98be1e527abd576533293c634f6a" + "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", - "reference": "4bfa827c969c98be1e527abd576533293c634f6a", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", + "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -8528,40 +8612,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/6.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/object-reflector", + "type": "tidelift" } ], - "time": "2025-02-07T04:58:17+00:00" + "time": "2026-02-06T04:47:13+00:00" }, { "name": "sebastian/recursion-context", - "version": "7.0.1", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" + "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", - "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/74c5af21f6a5833e91767ca068c4d3dfec15317e", + "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "7.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -8592,7 +8688,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/8.0.0" }, "funding": [ { @@ -8612,32 +8708,32 @@ "type": "tidelift" } ], - "time": "2025-08-13T04:44:59+00:00" + "time": "2026-02-06T04:51:28+00:00" }, { "name": "sebastian/type", - "version": "6.0.3", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d" + "reference": "42412224607bd3931241bbd17f38e0f972f5a916" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d", - "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/42412224607bd3931241bbd17f38e0f972f5a916", + "reference": "42412224607bd3931241bbd17f38e0f972f5a916", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "phpunit/phpunit": "^13.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -8661,7 +8757,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/6.0.3" + "source": "https://github.com/sebastianbergmann/type/tree/7.0.0" }, "funding": [ { @@ -8681,29 +8777,29 @@ "type": "tidelift" } ], - "time": "2025-08-09T06:57:12+00:00" + "time": "2026-02-06T04:52:09+00:00" }, { "name": "sebastian/version", - "version": "6.0.0", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" + "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", - "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ad37a5552c8e2b88572249fdc19b6da7792e021b", + "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b", "shasum": "" }, "require": { - "php": ">=8.3" + "php": ">=8.4" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -8727,15 +8823,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/version/issues", "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" + "source": "https://github.com/sebastianbergmann/version/tree/7.0.0" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/version", + "type": "tidelift" } ], - "time": "2025-02-07T05:00:38+00:00" + "time": "2026-02-06T04:52:52+00:00" }, { "name": "staabm/side-effects-detector", diff --git a/rector.php b/rector.php index 3569811c9..d6feedb7b 100644 --- a/rector.php +++ b/rector.php @@ -19,6 +19,7 @@ privatization: true, naming: true, rectorPreset: true, + phpunitCodeQuality: true ) ->withPhpSets() ->withAttributesSets() diff --git a/resources/blog/posts/2026/2026-02-07-upgrade-to-phpunit-125-in-7-diffs.md b/resources/blog/posts/2026/2026-02-07-upgrade-to-phpunit-125-in-7-diffs.md index f1465e1ab..a5c5578ee 100644 --- a/resources/blog/posts/2026/2026-02-07-upgrade-to-phpunit-125-in-7-diffs.md +++ b/resources/blog/posts/2026/2026-02-07-upgrade-to-phpunit-125-in-7-diffs.md @@ -5,32 +5,51 @@ perex: | PHPUnit 12 was released a year ago, but only PHPUnit 12.5 released in December 2025 includes valuable features that are worth the once a year ugprade. The most important change, that will affect your code, is that mocks are now much more strict. And there also stubs... a mock that does not nothing. How to spot them and separate them? + + Are you curious how to get from 4000 notices to under 100 in 7 diffs? Read on. --- What is difference between a mock and a stub? You didn't have to care untill PHPUnit 12.5, but now you do. -PHPUnit now complains miss-use verboselly, and there is no way to ignore it: +Why? Because PHPUnit now complains their miss-use verboselly. And there is no way to ignore it: -There is much more complex definition in the PHPUnit docs, but in plain English: +
+
+ +There is more precise definition in the PHPUnit docs, but in plain English: + +
+"What is a difference between a mock and a stub?" +
+ +
* **a mock** is a fake class, that has expectations of being called or not being called, ```php $someMock = $this->createMock(SomeClass::class); -$someMock->expects($this->once())->method('someMethod')->willReturn(100); +$someMock->expects($this->once()) + ->method('someMethod') + ->willReturn(100); ``` -We expect the `someMethod` to be called, or PHPUnit will crash. +Here we expect the `someMethod` to be called. PHPUnit will crash with error otherwise. + +
* **a stub** is also a fake class, but it doesn't do anything at all. +We can use it to make comply with constructor requirements: + ```php $someClass = new SomeClass($this->createStub(SomeDependency::class)); ``` -We can use it to make comply with constructor requirements +
+ +We can also use it to assert the same object is used on a getter call later: ```php $request = $this->createStub(Request::class); @@ -39,20 +58,408 @@ $requestStack = new RequestStack($request); $this->assertSame($request, $requestStack->getCurrentRequest()); ``` -We can also use it to assert the same object is used on a getter call later. -
This leads us to first simplest change we can do.
-## 1. Use `createStub()` over `createMock()`, where no expectations +## 1. Use `createStub()` over `createMock()` in args + +The first one are simple as: + +```diff + $someClass = new SomeClass( +- $this->createMock(SomeDependency::class) ++ $this->createStub(SomeDependency::class) + ); +``` + +
+ +Also variable assigns: + +```diff +-$someDependency = $this->createMock(SomeDependency::class); ++$someDependency = $this->createStub(SomeDependency::class); + + $someClass = new SomeClass($someDependency); +``` + +Or coalesce as argument: + +```diff +-$someClass = new SomeClass( + $someInput ?? $this->createMock(SomeDependency::class), + $someInput ?? $this->createStub(SomeDependency::class), + ); +``` + +
+ +But also property fetches without any expectations: + +```diff + protected function setUp() + { +- $this->someDependency = $this->createMock(SomeDependency::class); ++ $this->someDependency = $this->createStub(SomeDependency::class); + } + + + public function test() + { + $someClass = new SomeClass($this->someDependency); + } +``` + +
+ +## 2. Inline once-used Mocks Property to a Variable + +This is not change in PHPUnit 12.5 per se, bit it helps with changes that come with it. During the upgrade, I've noticed some properties are used just once. + +Properties are not variables for one reason: to be used across multiple methods. So lets fix that: + +```diff +-private MockObject $someDependency; + + protected function setUp() + { +- $this->someDependency = $this->createMock(SomeDependency::class); + } + + + public function test() + { +- $someClass = new SomeClass($this->someDependency); ++ $someClass = new SomeClass($this->createStub(SomeDependency::class)); + } +``` + +We have less code to read for us and GPT, and also can move to `createStub()` without any doubts. + +
+ +
+ +## 3. Remove never used isolated mocks, as well as dead + +Speaking of dead code, the mocks to stub narrowign also surfaces another issue: never used mocks that live on their own island. + +
+ +PHPUnit being more stricter now helps us find code that was never evaluated and only taking our reading space. + +```php +$this->createMock(SomeClass::class) + ->method('someMethod') + ->with($this->isInstanceOf(InputArgument::class)) + ->willReturn(100); +``` + +What is wrong with this code snippet, apart being a stub? It is never used. We created it, but we never assigned it to a variable, nor property feath, nor argument of a method call. + +It's a dead code. Remove it: + +```diff +-$this->createMock(SomeClass::class) +- ->method('someMethod') +- ->with($this->isInstanceOf(InputArgument::class)) +- ->willReturn(100); +``` + +
+ +Beware, this can be a as complex as well defined and typed property... that is never used. Dead code, remove it: + +```diff +-private MockObject $mockProperty; + + protected function setUp(): void + { +- $this->mockProperty = $this->createMock(\stdClass::class); +- $this->mockProperty->expects($this->once()) +- ->method('someMethod') +- ->willReturn('someValue'); + } +``` + +
+ +## 4. From `$this->any()` to explicit expectations + +PHPUnit now also deprecated used of `$this->any()` expectations. Wise choice, as its says "we expect 0, or 1 or any number of occurances". This code as well could be removed. + +
+ +Following code snippets have the same meaning: + +```php +$someClass = $this->createMock(SomeClass::class); + +$someClass->expects($this->any()) + ->method('someMethod') + ->willReturn(100); + +// same as +$someClass + ->method('someMethod') + ->willReturn(100); +``` + +Both will be most reported by PHPUnit as stubs. They have 0 expectations (amongs other numbers). So how do we fix that? Change we used before will not be enought (nor working): ```diff +-$someClass = $this->createMock(SomeClass::class); ++$someClass = $this->createStub(SomeClass::class); +``` + +
+We have to be honest here, and it might require to understand the code. + +* Is it a dummy method defined in `setUp()` method, in case it will be called any further in the codebase? +* Is it implicit `$this->any()`, just becaused we forgot to add explicit number? + +
+ +The most common case in codebases I work with was the second one: + +```diff +-$someClass->expects($this->any()) ++$someClass->expects($this->atLeastOnce()) + ->method('someMethod') + ->willReturn(100); ``` +But what about the `setUp()` method? Do we have to now go through all the code and inline all the properties? This hurts just writing it. This gets us to our next change: + +
+ + +## 5. Add `#[AllowMockObjectsWithoutExpectations]` for setUp() optionals + +It's perfectly reasonable to use `setUp()` method to create mock properties that may or may not be used in one of the test method later: + +```php +private SomeObject $someDependency; + +protected function setUp(): void +{ + $this->someDependency = $this->createMock(SomeDependency::class) + // implicit ->expects($this->any()) + ->method('someMethod') + ->willReturn(100); +} + + +public function testUsing() +{ + $someClass = new SomeClass($this->someDependency); + // ... +} + +public function testNotUsing() +{ + $someClass = new SomeClass(new AnotherDependnecy()); + // ... +} +``` + +Here we have one mocked object as a property with *any* expectations. Then 2 test methods. The 1st one is using mock as a mock. +The 2nd test method is not, so it's a stub from its point of view. + +(Also, another method can be using the property, but never calling the mocked method, so it's a stub as well). + +
+ +An attribute to the rescue! + +```diff + use PHPUnit\Framework\TestCase; ++use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations; + ++#[AllowMockObjectsWithoutExpectations] + final class SomeTest extends TestCase + { + private SomeObject $someDependency; + + // ... + } +``` + +This attribute will silence the notices about stubs in this test class. + +
+ +We could use it on every case above, yes. But that would prevent us from obvious fixes and push the technical debt deeper under the rug with whole under our apparment. + +
+ + +## 6. Cover Vendor `*TestCase` and Data Providers + +There are 2 more case where the `#[AllowMockObjectsWithoutExpectations]` attribute is needed and makes sense. + +
+ +We use a 3rd party test case class, that defines its "any" expectations for a reason. They might be used, or not. Depends on how we write the test. + +```diff + use Symfony\Component\Form\Test\TypeTestCase; + ++#[AllowMockObjectsWithoutExpectations] + final class SomeTest extends TypeTestCase + { + // ... + } +``` + +
+ +The next is a test method that uses a data provider. The data provider usually tests edge-case values that may or may not trigger a method call: + +```diff + use PHPUnit\Framework\TestCase; + ++#[AllowMockObjectsWithoutExpectations] + final class SomeTest extends TestCase + { + #[DataProvider('provideData')] + public function test($input) + { + $someClass = $this->createMock(SomeClass::class); + $someClass + // implicit $this->any() here + ->expects($this->atLeastOnce()) + ->method('someMethod') + ->willReturn(100); + + // ... + } + + public static function provideData(): iterable + { + // ... + } + } +``` + +
+ +## 7. Move from Object mocking to... Objects + +
+"The best mock is no mock at all" +
+ +Before we event dived into PHPUnit upgrade, we first eliminated the obvious cases that don't need any mocking at all. + +We looked for plain objects, DTOs, value objects, entities and documents and replacted them will 100 % real, natively typed objects. + +
+ +It can be as simple as using a simple `Request` directly: + +```diff + use PHPUnit\Framework\TestCase; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\RequestStack; + + final class SomeTest extends TestCase + { + public function test() + { +- $request = $this->createMock(Request::class); ++ $request = new Request(); + +- $requestStack = $this->createMock(RequestStack::class); +- $requestStack->expects($this->atLeastOnce()) +- ->method('getMainRequest') +- ->willReturn($request); ++ $requestStack = new RequestStack($request); + + $this->someMethod($requestStack); + } +} +``` + +
+ +Simple as that. Same applies for entity/document objects. Instead of hard-to-read gettter mocks, use real objects with real values and types: + +```diff +-$user = $this->createMock(User::class); ++$user = new User(); + +-$user->expects($this->any()) +- ->method('getName') +- ->willReturn('Tomas'); ++$user->setName('Tomas'); + +-$user->expects($this->any()) +- ->method('getAge') +- ->willReturn($age); ++$user->setAge($age); + + $service->process($user); +``` + +
+ +You can get the entity/document PHPStan spotter rule from `symplify/phpstan-rules` [here](https://github.com/symplify/phpstan-rules/blob/4b7aa41072850f9875b45272d263be3f4a183f40/src/Rules/Doctrine/NoDocumentMockingRule.php#L21). + +Also, give a go to experimental [Rector rule](https://github.com/rectorphp/rector-phpunit/pull/629) that manages to change these mocks to entities. It's a real time-saver. + + +
+ +## Enjoy Automated Upgrade + +We automated most of this work above, so you can let your agent handle the rest of the edge-cases. To get there, first enable the `phpunitCodeQuality` prepared set in your `rector.php`: + +```php +use Rector\Config\RectorConfig; + +return RectorConfig::configure() + ->withPreparedSets(phpunitCodeQuality: true) +``` + +And run Rector: + +```bash +vendor/bin/rector +``` + +
+ +Only then upgrade to PHPUnit 12.5 and run Rector with composer based set: + +```php +use Rector\Config\RectorConfig; + +return RectorConfig::configure() + ->withComposerBased(phpunit: true) +``` + +And run Rector again: + +```bash +vendor/bin/rector +``` + +It will automatically pick up the PHPUnit version and apply [the 12.5 set](https://github.com/rectorphp/rector-phpunit/blob/main/config/sets/phpunit120.php). + +
+ +That's all folks. Hope you've enjoyed this manually-written post. I surelly did enjoy writing it. + +As always, if you have improvement or bug report, head to Rector on Github and let us know. + +
+ +Happy coding! From c0be99a543d3992cd0fa5119bc85e3906af700ed Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 6 Feb 2026 12:50:10 +0100 Subject: [PATCH 3/4] gram --- ...02-07-upgrade-to-phpunit-125-in-7-diffs.md | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/resources/blog/posts/2026/2026-02-07-upgrade-to-phpunit-125-in-7-diffs.md b/resources/blog/posts/2026/2026-02-07-upgrade-to-phpunit-125-in-7-diffs.md index a5c5578ee..f72cdb423 100644 --- a/resources/blog/posts/2026/2026-02-07-upgrade-to-phpunit-125-in-7-diffs.md +++ b/resources/blog/posts/2026/2026-02-07-upgrade-to-phpunit-125-in-7-diffs.md @@ -1,17 +1,17 @@ --- id: 83 -title: "Upgrade to PHPUnit 12.5 in 7 diffs" +title: "Upgrade to PHPUnit 12.5 in 7 Diffs" perex: | - PHPUnit 12 was released a year ago, but only PHPUnit 12.5 released in December 2025 includes valuable features that are worth the once a year ugprade. + PHPUnit 12 was released a year ago, but only PHPUnit 12.5 released in December 2025 includes valuable features that are worth it. - The most important change, that will affect your code, is that mocks are now much more strict. And there also stubs... a mock that does not nothing. How to spot them and separate them? + The most important change, that will affect your code, is that mocks are now much stricter. There are also stubs, a mock that does nothing. How do you spot them and separate them? - Are you curious how to get from 4000 notices to under 100 in 7 diffs? Read on. + Curious how to get from 4000 notices to under 100 in 7 diffs? Read on. --- -What is difference between a mock and a stub? You didn't have to care untill PHPUnit 12.5, but now you do. +What is the difference between a mock and a stub? You did not have to care until PHPUnit 12.5, but now you do. -Why? Because PHPUnit now complains their miss-use verboselly. And there is no way to ignore it: +Why? Because PHPUnit now complains about their misuse very verbosely. There is no way to ignore it: @@ -26,7 +26,7 @@ There is more precise definition in the PHPUnit docs, but in plain English:
-* **a mock** is a fake class, that has expectations of being called or not being called, +* **A mock** is a fake class that has expectations about being called or not being called, ```php $someMock = $this->createMock(SomeClass::class); @@ -39,7 +39,7 @@ Here we expect the `someMethod` to be called. PHPUnit will crash with error othe
-* **a stub** is also a fake class, but it doesn't do anything at all. +* **A stub** is also a fake class, but it does not do anything at all. We can use it to make comply with constructor requirements: @@ -60,13 +60,13 @@ $this->assertSame($request, $requestStack->getCurrentRequest());
-This leads us to first simplest change we can do. +This leads us to the first and simplest change we can make.
-## 1. Use `createStub()` over `createMock()` in args +## 1. Use `createStub()` instead of `createMock()` in arguments -The first one are simple as: +The first cases are as simple as: ```diff $someClass = new SomeClass( @@ -86,7 +86,7 @@ Also variable assigns: $someClass = new SomeClass($someDependency); ``` -Or coalesce as argument: +Or coalesce directly in the argument: ```diff -$someClass = new SomeClass( @@ -117,9 +117,9 @@ But also property fetches without any expectations: ## 2. Inline once-used Mocks Property to a Variable -This is not change in PHPUnit 12.5 per se, bit it helps with changes that come with it. During the upgrade, I've noticed some properties are used just once. +This is not a change in PHPUnit 12.5 itself, but it helps with the changes that come with it. During the upgrade, I've noticed some properties are used just once. -Properties are not variables for one reason: to be used across multiple methods. So lets fix that: +Properties are not variables for one reason: to be used across multiple methods. Let us fix that: ```diff -private MockObject $someDependency; @@ -143,7 +143,7 @@ We have less code to read for us and GPT, and also can move to `createStub()` wi
-## 3. Remove never used isolated mocks, as well as dead +## 3. Remove never used isolated mocks and dead code Speaking of dead code, the mocks to stub narrowign also surfaces another issue: never used mocks that live on their own island. @@ -158,9 +158,9 @@ $this->createMock(SomeClass::class) ->willReturn(100); ``` -What is wrong with this code snippet, apart being a stub? It is never used. We created it, but we never assigned it to a variable, nor property feath, nor argument of a method call. +What is wrong with this code snippet, apart from being a stub? It is never used. We created it, but we never assigned it to a variable, nor property feath, nor argument of a method call. -It's a dead code. Remove it: +It is dead code. Remove it: ```diff -$this->createMock(SomeClass::class) @@ -171,7 +171,7 @@ It's a dead code. Remove it:
-Beware, this can be a as complex as well defined and typed property... that is never used. Dead code, remove it: +Beware, this can be as complex as a well defined and typed property... that is never used. Dead code, remove it: ```diff -private MockObject $mockProperty; @@ -189,7 +189,7 @@ Beware, this can be a as complex as well defined and typed property... that is n ## 4. From `$this->any()` to explicit expectations -PHPUnit now also deprecated used of `$this->any()` expectations. Wise choice, as its says "we expect 0, or 1 or any number of occurances". This code as well could be removed. +PHPUnit now also deprecated used of `$this->any()` expectations. This is a wise choice, as it effectively says "we expect 0, 1, or any number of occurrences". This code as well could be removed.
@@ -208,7 +208,7 @@ $someClass ->willReturn(100); ``` -Both will be most reported by PHPUnit as stubs. They have 0 expectations (amongs other numbers). So how do we fix that? Change we used before will not be enought (nor working): +Both will be most reported by PHPUnit as stubs. They have 0 expectations (among other numbers). So how do we fix that? Change we used before is not enough and will not work here: ```diff -$someClass = $this->createMock(SomeClass::class); @@ -220,7 +220,7 @@ Both will be most reported by PHPUnit as stubs. They have 0 expectations (amongs We have to be honest here, and it might require to understand the code. * Is it a dummy method defined in `setUp()` method, in case it will be called any further in the codebase? -* Is it implicit `$this->any()`, just becaused we forgot to add explicit number? +* Is it implicit `$this->any()`, just because we forgot to add explicit number?
@@ -238,7 +238,7 @@ But what about the `setUp()` method? Do we have to now go through all the code a
-## 5. Add `#[AllowMockObjectsWithoutExpectations]` for setUp() optionals +## 5. Add `#[AllowMockObjectsWithoutExpectations]` for optional setUp mocks It's perfectly reasonable to use `setUp()` method to create mock properties that may or may not be used in one of the test method later: @@ -267,8 +267,8 @@ public function testNotUsing() } ``` -Here we have one mocked object as a property with *any* expectations. Then 2 test methods. The 1st one is using mock as a mock. -The 2nd test method is not, so it's a stub from its point of view. +Here we have one mocked object as a property with *any* expectations. Then there are two test methods. The first one uses the mock as a mock. +The second test method does not, so from its point of view it is a stub. (Also, another method can be using the property, but never calling the mocked method, so it's a stub as well). @@ -293,14 +293,14 @@ This attribute will silence the notices about stubs in this test class.
-We could use it on every case above, yes. But that would prevent us from obvious fixes and push the technical debt deeper under the rug with whole under our apparment. +We could use it on every case above, yes. But that would prevent us from obvious fixes and push the technical debt deeper under the rug with a hole under our apartment.
-## 6. Cover Vendor `*TestCase` and Data Providers +## 6. Cover vendor `*TestCase` classes and data providers -There are 2 more case where the `#[AllowMockObjectsWithoutExpectations]` attribute is needed and makes sense. +There are two more cases where the `#[AllowMockObjectsWithoutExpectations]` attribute is needed and makes sense.
@@ -318,7 +318,7 @@ We use a 3rd party test case class, that defines its "any" expectations for a re
-The next is a test method that uses a data provider. The data provider usually tests edge-case values that may or may not trigger a method call: +The next is a test method that uses a data provider. The data provider usually tests edge case values that may or may not trigger a method call: ```diff use PHPUnit\Framework\TestCase; @@ -348,15 +348,15 @@ The next is a test method that uses a data provider. The data provider usually t
-## 7. Move from Object mocking to... Objects +## 7. Move from object mocking to real objects
"The best mock is no mock at all"
-Before we event dived into PHPUnit upgrade, we first eliminated the obvious cases that don't need any mocking at all. +Before we even started the PHPUnit upgrade, we first eliminated the obvious cases that don't need any mocking at all. -We looked for plain objects, DTOs, value objects, entities and documents and replacted them will 100 % real, natively typed objects. +We looked for plain objects, DTOs, value objects, entities and documents and replaced them with real, natively typed objects.
@@ -387,7 +387,7 @@ It can be as simple as using a simple `Request` directly:
-Simple as that. Same applies for entity/document objects. Instead of hard-to-read gettter mocks, use real objects with real values and types: +Simple as that. Same applies for entity/document objects. Instead of hard-to-read getter mocks, use real objects with real values and types: ```diff -$user = $this->createMock(User::class); @@ -410,12 +410,12 @@ Simple as that. Same applies for entity/document objects. Instead of hard-to-rea You can get the entity/document PHPStan spotter rule from `symplify/phpstan-rules` [here](https://github.com/symplify/phpstan-rules/blob/4b7aa41072850f9875b45272d263be3f4a183f40/src/Rules/Doctrine/NoDocumentMockingRule.php#L21). -Also, give a go to experimental [Rector rule](https://github.com/rectorphp/rector-phpunit/pull/629) that manages to change these mocks to entities. It's a real time-saver. +Also, give a go to experimental [Rector rule](https://github.com/rectorphp/rector-phpunit/pull/629) that manages to change these mocks to entities. It is a real time saver.
-## Enjoy Automated Upgrade +## Enjoy the Automated Upgrade We automated most of this work above, so you can let your agent handle the rest of the edge-cases. To get there, first enable the `phpunitCodeQuality` prepared set in your `rector.php`: @@ -453,7 +453,7 @@ It will automatically pick up the PHPUnit version and apply [the 12.5 set](https
-That's all folks. Hope you've enjoyed this manually-written post. I surelly did enjoy writing it. +That's all folks. I hope you enjoyed this manually written post. I certainly enjoyed writing it. As always, if you have improvement or bug report, head to Rector on Github and let us know. From c769a49889e053956fb1c3cfbec553c0d44eb22b Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 6 Feb 2026 11:50:17 +0000 Subject: [PATCH 4/4] [rector] Rector fixes --- tests/Controller/DemoControllerTest.php | 2 -- tests/Documentation/DocumentationMenuFactoryTest.php | 2 +- tests/FileSystem/RectorFinderTest.php | 4 ++-- .../BodyFactory/FixtureBodyFactory/FixtureBodyFactoryTest.php | 2 -- .../BodyFactory/IssueBodyFactory/IssueBodyFactoryTest.php | 2 -- .../PullRequestDescriptionFactoryTest.php | 2 -- .../LinkFactory/FixtureLinkFactory/FixtureLinkFactoryTest.php | 2 -- 7 files changed, 3 insertions(+), 13 deletions(-) diff --git a/tests/Controller/DemoControllerTest.php b/tests/Controller/DemoControllerTest.php index 357bd155d..067a6a5ff 100644 --- a/tests/Controller/DemoControllerTest.php +++ b/tests/Controller/DemoControllerTest.php @@ -16,11 +16,9 @@ final class DemoControllerTest extends AbstractTestCase { - #[Override] protected function setUp(): void { parent::setUp(); - $this->withoutMiddleware(ValidateCsrfToken::class); } diff --git a/tests/Documentation/DocumentationMenuFactoryTest.php b/tests/Documentation/DocumentationMenuFactoryTest.php index e9dd1d769..9ed69d2a5 100644 --- a/tests/Documentation/DocumentationMenuFactoryTest.php +++ b/tests/Documentation/DocumentationMenuFactoryTest.php @@ -17,7 +17,7 @@ final class DocumentationMenuFactoryTest extends TestCase protected function setUp(): void { $urlGenerator = $this->createMock(UrlGenerator::class); - $urlGenerator->expects($this->any()) + $urlGenerator ->method('action') ->willReturn('/index.html'); diff --git a/tests/FileSystem/RectorFinderTest.php b/tests/FileSystem/RectorFinderTest.php index 224816754..d529d88f6 100644 --- a/tests/FileSystem/RectorFinderTest.php +++ b/tests/FileSystem/RectorFinderTest.php @@ -31,9 +31,9 @@ public function testFindDuplicated(): void $uniqueShortNames = array_unique($shortNames); $uniqueLongNames = array_unique($longNames); - $this->assertSame( + $this->assertCount( count($uniqueShortNames), - count($uniqueLongNames), + $uniqueLongNames, 'There are no duplicated short class names.' ); } diff --git a/tests/GitHubMagicLink/BodyFactory/FixtureBodyFactory/FixtureBodyFactoryTest.php b/tests/GitHubMagicLink/BodyFactory/FixtureBodyFactory/FixtureBodyFactoryTest.php index a94ec6654..01ff3a859 100644 --- a/tests/GitHubMagicLink/BodyFactory/FixtureBodyFactory/FixtureBodyFactoryTest.php +++ b/tests/GitHubMagicLink/BodyFactory/FixtureBodyFactory/FixtureBodyFactoryTest.php @@ -13,11 +13,9 @@ final class FixtureBodyFactoryTest extends AbstractTestCase { private FixtureBodyFactory $fixtureBodyFactory; - #[Override] protected function setUp(): void { parent::setUp(); - $this->fixtureBodyFactory = $this->make(FixtureBodyFactory::class); } diff --git a/tests/GitHubMagicLink/BodyFactory/IssueBodyFactory/IssueBodyFactoryTest.php b/tests/GitHubMagicLink/BodyFactory/IssueBodyFactory/IssueBodyFactoryTest.php index 3f5c1e094..272878651 100644 --- a/tests/GitHubMagicLink/BodyFactory/IssueBodyFactory/IssueBodyFactoryTest.php +++ b/tests/GitHubMagicLink/BodyFactory/IssueBodyFactory/IssueBodyFactoryTest.php @@ -13,11 +13,9 @@ final class IssueBodyFactoryTest extends AbstractTestCase { private IssueBodyFactory $issueBodyFactory; - #[Override] protected function setUp(): void { parent::setUp(); - $this->issueBodyFactory = $this->make(IssueBodyFactory::class); } diff --git a/tests/GitHubMagicLink/BodyFactory/PullRequestDescriptionFactory/PullRequestDescriptionFactoryTest.php b/tests/GitHubMagicLink/BodyFactory/PullRequestDescriptionFactory/PullRequestDescriptionFactoryTest.php index e045ff9b6..dd7703bf4 100644 --- a/tests/GitHubMagicLink/BodyFactory/PullRequestDescriptionFactory/PullRequestDescriptionFactoryTest.php +++ b/tests/GitHubMagicLink/BodyFactory/PullRequestDescriptionFactory/PullRequestDescriptionFactoryTest.php @@ -13,11 +13,9 @@ final class PullRequestDescriptionFactoryTest extends AbstractTestCase { private PullRequestDescriptionFactory $pullRequestDescriptionFactory; - #[Override] protected function setUp(): void { parent::setUp(); - $this->pullRequestDescriptionFactory = $this->make(PullRequestDescriptionFactory::class); } diff --git a/tests/GitHubMagicLink/LinkFactory/FixtureLinkFactory/FixtureLinkFactoryTest.php b/tests/GitHubMagicLink/LinkFactory/FixtureLinkFactory/FixtureLinkFactoryTest.php index a8d8505e0..0fbf6bcc3 100644 --- a/tests/GitHubMagicLink/LinkFactory/FixtureLinkFactory/FixtureLinkFactoryTest.php +++ b/tests/GitHubMagicLink/LinkFactory/FixtureLinkFactory/FixtureLinkFactoryTest.php @@ -18,11 +18,9 @@ final class FixtureLinkFactoryTest extends AbstractTestCase { private FixtureLinkFactory $testFixtureLinkFactory; - #[Override] protected function setUp(): void { parent::setUp(); - $this->testFixtureLinkFactory = $this->make(FixtureLinkFactory::class); }